C/C++动态内存管理,智能指针及RAlI思想。

一、C 语言动态内存管理(malloc/calloc/realloc/free)

四个核心函数

都在 <cstdlib>,操作原始堆内存 ,只分配 / 释放内存,不会调用构造、析构函数。

1、void* malloc(size_t size)

分配 size 字节未初始化堆内存;分配失败返回 NULL。

复制代码
int* arr = (int*)malloc(sizeof(int) * 10);

2、void* calloc(size_t num, size_t size)

分配 num 个、每个 size 字节,分配后内存全部置 0。

3、void* realloc(void* ptr, size_t new_size)扩容 / 缩容已有内存;

ptr 为 NULL:等价 malloc;

new_size=0:等价 free;原内存数据会拷贝到新空间,旧内存自动释放。

4、void free(void* ptr)释放堆内存,只能释放 malloc/calloc/realloc 分配的内存;

free 空指针安全;

重复 free、free 栈变量 / 全局变量 → 未定义行为(崩溃)。

二、C++ 原生动态内存 new /delete

1. 基础用法

复制代码
// 单个对象
int* p = new int(10);
delete p;

// 对象数组
int* arr = new int[5]{1,2,3,4,5};
delete[] arr; // 数组必须带[],否则只调用第一个元素析构,内存错乱

2. new 底层两步

  1. operator new:底层调用 malloc 开辟堆内存;
  2. 调用构造函数初始化对象。

3. delete 底层两步

  1. 调用对象析构函数,释放对象内部资源;
  2. operator delete 调用 free 归还堆内存。

4. new/delete 相比 malloc/free 的优势

  1. 自动调用构造、析构,适配 C++ 类、自定义对象;
  2. 自带类型安全,无需强制类型转换;
  3. 支持数组语法,区分单个对象与数组释放。

5. new/delete 存在大量问题

  1. 忘记 delete → 内存泄漏;
  2. 同一块内存多次 delete / 栈指针 delete → 程序崩溃;
  3. 函数中途抛异常、提前 return,delete 执行不到;
  4. 多函数传递裸指针,内存所有权模糊,没人负责释放;
  5. 异常安全差:new 成功后,如果后续代码抛异常,delete 不会执行。

根本解决方案:RAII + 智能指针

三、RAII 核心思想(Resource Acquisition Is Initialization 资源获取即初始化)

1. 核心定义

把资源获取 写在类的构造函数 ,资源释放 写在类的析构函数 ;利用栈对象自动生命周期:栈上局部对象离开作用域(正常 return、break、抛出异常)时,编译器一定会自动调用析构函数,保证资源释放。

四、C++ 标准智能指针(<memory>,全部基于 RAII 实现)

智能指针本质是封装裸指针的栈对象 ,依靠 RAII 自动释放堆内存,彻底替代手动 delete。分三类:unique_ptr、shared_ptr、weak_ptr

1. std::unique_ptr 独占智能指针(最常用)

特性
  1. 独占所有权:一块内存只能被一个 unique_ptr 持有;
  2. 禁止拷贝构造 / 拷贝赋值 ,只支持移动语义 std::move;
  3. 无引用计数,运行时开销几乎和裸指针一致,性能最优;
代码示例
复制代码
// 推荐,一步分配内存,异常安全
auto up = std::make_unique<int>(666);
// auto up2 = up;  // 编译报错,禁止拷贝
auto up2 = std::move(up); // 所有权转移,up变为空
// 离开作用域自动释放内存
适用场景

资源只需要单一管理者,绝大多数堆内存场景优先 unique_ptr。

.最常用成员函数

  1. get():获取裸指针
  2. reset():释放 / 更换资源
  3. release():交出裸指针,自身置空

2. std::shared_ptr 共享智能指针

特性
  1. 共享所有权 :内部维护原子引用计数器;
  2. 支持拷贝:拷贝时引用计数 + 1;智能指针销毁 / 覆盖赋值时计数 - 1;
  3. 引用计数变为 0 时,自动释放堆内存;
  4. 性能损耗:原子操作多线程同步,开销高于 unique_ptr;
  5. 致命缺陷:循环引用会导致内存泄漏。
代码示例
复制代码
auto sp1 = std::make_shared<std::string>("test");
auto sp2 = sp1; // 计数+1,两个指针共用一块内存

.最常用成员函数

  • get():获取裸指针
  • reset():释放 / 更换资源
  • use_count():获取当前引用计数
循环引用泄漏场景(面试高频)

两个类互相持有对方 shared_ptr,引用计数永远不为 0,内存无法释放:

复制代码
struct A {
    std::shared_ptr<B> b;
};
struct B {
    std::shared_ptr<A> a;
};

void leakDemo() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->b = b;
    b->a = a;
    // 函数结束,a、b销毁,但互相持有计数,内存泄漏
}

3. std::weak_ptr 弱智能指针(配套 shared_ptr 解决循环引用)

特性
  1. 不增加引用计数,不拥有内存所有权,仅做 "观察者";
  2. 只能由 shared_ptr 构造;
  3. 使用前调用 .lock() 升级为 shared_ptr,判断内存是否存活(.expired())。
改造上面循环引用,解决泄漏
复制代码
struct A {
    std::shared_ptr<B> b;
};
struct B {
    std::weak_ptr<A> a; // 改用weak_ptr,不增加计数
};

常用成员函数

  • lock():升级成 shared_ptr(用来访问对象)
  • expired():判断对象是否已经销毁
  • reset():清空弱指针
  • use_count():查看强引用数量

4. make_unique /make_shared 为什么比 new 好?

  1. 内存分配一步到位:make_shared 把控制块 + 对象内存分配在同一块连续堆,减少内存碎片,缓存友好;
  2. 异常安全 :若 func(std::shared_ptr<T>(new T), func_throw()),编译器可能先执行 new T 分配内存,再执行抛异常函数,内存分配成功但无智能指针接管,直接泄漏;make 系列将分配和构造绑定,不会出现裸内存悬空。
相关推荐
Frank_refuel36 分钟前
C++11之一场名为“搬家”的 C++ 之旅
开发语言·c++
Wang's Blog1 小时前
Java 项目实战: 外卖平台优化-Nginx配置文件结构与块层级
java·开发语言·nginx
hetao17338373 小时前
2026-10-03~04 hetao1733837 的刷题记录
c++·算法
\光辉岁月/3 小时前
5.java-数组
java·开发语言
谢亮_vipxieliang3 小时前
Spring 事务失效的常见场景
java·开发语言·数据库·spring boot
longlongzihan3 小时前
从LeetCode 70. 爬楼梯看动态规划:如何精准推导状态转移方程?
c++·算法·leetcode·动态规划
郑州光合科技余经理4 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
2601_962071574 小时前
类变量和全局变量的生命周期有什么区别?
java·开发语言·jvm
code2cat4 小时前
Java进阶篇之AtomicReference:一次替换引用,读到一份完整状态
java·并发编程·不可变对象·atomicreference
专业程序开发源4 小时前
springboot简历管理系统81389-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计