核心要点速览
- 动态内存:运行时手动分配,需手动释放(否则泄漏);new/delete 自动调用构造 / 析构,需与 new []/delete [] 严格匹配
- 智能指针(C++11+,
<memory>):RAII 机制自动管理内存,unique_ptr(独占)、shared_ptr(共享,引用计数)、weak_ptr(解循环引用) - 常见问题:内存泄漏(未释放)、野指针(未初始化)、悬空指针(指向已释放内存)
一、动态内存的分配与释放
核心是 “分配 - 使用 - 释放” 闭环,C++ 推荐用 new/delete,需严格匹配使用。
1. C vs C++ 分配释放方式对比
| 对比维度 | C(malloc/free) | C++(new/delete) |
|---|---|---|
| 类型安全 | 返回void*,需手动强转(不安全) | 返回对应类型指针(无需转换,安全) |
| 构造 / 析构 | 不调用(仅操作内存) | 自动调用构造(new)/ 析构(delete) |
| 数组支持 | 需手动计算总大小(n*sizeof(T)) | 直接new T[n](自动计算大小) |
| 分配失败 | 返回NULL | 默认抛bad_alloc,可指定nothrow返回NULL |
| 重载扩展 | 不可重载 | 可重载operator new/operator delete定制分配 |
2. 匹配规则
- 单个对象:
new T→delete ptr(匹配单个构造 / 析构) - 数组对象:
new T[n]→delete[] ptr(匹配 n 次构造 / 析构) - 不匹配后果:
delete释放new[]:仅第一个元素析构,其余资源泄漏,可能崩溃delete[]释放new:错误读取数组长度,多次析构,破坏内存状态
3. 底层执行流程
new 的主要步骤
- 调用
operator new(sizeof(T)):分配原始内存(底层通常调用 malloc) - 调用构造函数:
new(p) T(args)(placement new,在指定内存构造对象) - 返回对象指针
delete 的主要步骤
- 调用析构函数:
p->~T()(清理对象资源) - 调用
operator delete(p):释放内存(底层通常调用 free)
二、常见动态内存问题
1. 内存泄漏
- 定义:动态内存不再使用但未释放,永久占用内存
- 核心原因:忘记释放、指针被重赋值(地址丢失)、异常导致释放代码未执行
- 解决方案:用智能指针、遵循 “谁分配谁释放”、RAII 机制(对象析构自动释放)
2. 野指针
- 定义:未初始化的指针(指向随机内存)
- 危害:解引用导致未定义行为(崩溃、数据错乱)
- 避免方案:定义时初始化(
int* p = nullptr;),释放后置为nullptr
3. 悬空指针
- 定义:指向已释放内存的指针(地址未置空)
- 危害:解引用访问无效内存(逻辑错误或崩溃)
- 避免方案:释放后立即置
nullptr,避免重复释放
野指针与悬空指针对比
| 类型 | 本质 | 产生场景 |
|---|---|---|
| 野指针 | 未初始化的指针 | int* p;(未赋值) |
| 悬空指针 | 指向已释放内存的指针 | delete p; 后未置 nullptr |
三、智能指针(C++11+,自动内存管理)
基于 RAII 机制,析构时自动释放内存,彻底解决手动管理的缺陷。
1. unique_ptr:独占所有权
- 特性:同一时间仅一个
unique_ptr指向内存,不可复制(禁用拷贝构造 / 赋值),可通过std::move转移所有权 - 适用场景:单一所有者的动态内存(如局部动态对象、函数返回动态对象)
- 补充:
- 零额外内存开销(仅封装原始指针)
- 支持数组管理:
unique_ptr<T[]>(自动调用delete[]) - 自定义删除器:用于管理非内存资源(如文件、锁)
auto arr = make_unique<int[]>(5); // 管理5个int的数组
arr[0] = 10; // 支持[]操作符
2. shared_ptr:共享所有权
- 特性:多个
shared_ptr共享同一内存,通过控制块维护引用计数(计数为 0 时自动释放) - 控制块内容:引用计数、弱引用计数、删除器、分配器
- 适用场景:多所有者的动态内存(如共享资源、容器中存储动态对象)
- 补充:
- 优先使用
make_shared<T>():一次性分配对象 + 控制块(高效,减少内存碎片),避免shared_ptr<T>(new T())(两次分配) - 线程安全:引用计数增减是原子操作,但对象读写需额外同步
- 优先使用
3. weak_ptr:解决循环引用
- 特性:弱引用(不增加引用计数),仅观测
shared_ptr管理的内存,不影响生命周期 - 核心用途:解决
shared_ptr的循环引用问题(如 A 和 B 互相持有shared_ptr,导致计数无法归零) - 主要操作:
lock()方法获取shared_ptr(有效则返回非空,无效则返回空) - 循环引用示例与解决方案:
class A { public: shared_ptr<B> b; };
class B { public: shared_ptr<A> a; };
// 循环引用:a和b的引用计数均为2,析构时无法归零,内存泄漏
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->b = b;
b->a = a;
// 解决方案:将其中一个改为weak_ptr(如B的a改为weak_ptr<A>)
4. 智能指针使用陷阱
- 避免用同一原始指针初始化多个
shared_ptr(导致重复释放) - 避免暴露原始指针(
get()方法):外部保存后可能导致悬空 unique_ptr转移所有权后,原指针失效(需避免再次使用)
四、问答
智能指针的底层实现原理?
- 智能指针是类模板,封装原始指针,通过 RAII 机制在析构时自动释放内存。
unique_ptr禁用拷贝实现独占;shared_ptr通过控制块存储引用计数,计数为 0 时释放;weak_ptr指向控制块,不增减计数,用于解循环引用。
76

被折叠的 条评论
为什么被折叠?



