6月1日 10:30
C++ 内存泄漏怎么避免?RAII/智能指针/检测工具实战指南
C++ 程序的内存分为栈(自动管理,函数结束释放)、堆(手动 new/delete)、全局/静态区(程序生命周期)和常量区(只读)。堆内存需开发者显式申请和释放,是内存泄漏的重灾区。泄漏的常见原因:new 后忘记 delete、异常导致跳过 delete、指针被覆写丢失地址。检测工具中,Valgrind 在运行时插桩检查,AddressSanitizer(ASan)由编译器插桩,开销更小、报错更直接。现代 C++ 的核心防御手段是 RAII 原则:资源获取即初始化,将资源生命周期绑定到栈对象,析构时自动释放。智能指针是 RAII 的典型应用:unique_ptr 独占所有权离开作用域自动释放,shared_ptr 引用计数归零释放,基本取代裸 new/delete。常见陷阱包括:悬空指针(访问已释放内存)、双重 delete(同一指针释放两次)、delete 配 new[] 或 delete[] 配 new 导致未定义行为。
追问
栈和堆的分配速度差异为何这么大?
栈分配只需移动栈顶指针,相当于一次整数加法。堆分配需遍历空闲链表查找合适块,可能触发系统调用(brk/mmap),还涉及内存碎片整理。
AddressSanitizer 和 Valgrind 怎么选?
开发阶段优先 ASan:编译时加 -fsanitize=address,运行开销约 2x,报错精确到源码行。Valgrind 无需重编译,但开销 10-20x,适合测试第三方二进制或无法重编译的场景。
循环引用导致 shared_ptr 泄漏怎么办?
将其中一方改为 weak_ptr,它不增加引用计数,打破循环。访问时通过 lock() 提升为 shared_ptr,若对象已释放则返回空。
RAII 只适用于内存吗?
不是。文件句柄、锁(lock_guard)、网络连接、数据库连接等有限资源都适用 RAII。核心思想是资源生命周期与对象绑定,析构函数负责释放。