[C++高频精进] 内存管理:动态内存

核心要点速览

  • 动态内存:运行时手动分配,需手动释放(否则泄漏);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 的主要步骤
  1. 调用operator new(sizeof(T)):分配原始内存(底层通常调用 malloc)
  2. 调用构造函数:new(p) T(args)(placement new,在指定内存构造对象)
  3. 返回对象指针
delete 的主要步骤
  1. 调用析构函数:p->~T()(清理对象资源)
  2. 调用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指向控制块,不增减计数,用于解循环引用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值