内存泄漏是动态分配内存后未释放,导致运行时内存持续增长并可能被oom杀死;c++中需严格配对new/delete、malloc/free,避免错配或遗漏;推荐用raii智能指针(unique_ptr/shared_ptr/weak_ptr)和工具(valgrind、asan)检测与预防。

内存泄漏就是 new 了没 delete,或 malloc 了没 free
它不是程序崩溃那一刻才出问题,而是像漏水的水管——每次运行都悄悄多占一点内存,跑得越久越卡,最终可能被系统 OOM 杀掉。C++ 没垃圾回收,new、new[]、malloc 这些调用后,必须有且仅有一次对应的 delete、delete[] 或 free;漏掉、重复、错配(比如 new 配 free),都算泄漏。
常见错误现象:
-
valgrind报definitely lost或possibly lost - 程序长时间运行后 RSS 内存持续上涨,
top或htop里看RES列不停变大 - 用
std::shared_ptr却在循环引用里忘了std::weak_ptr断环
用 valgrind 快速定位泄漏点(Linux/macOS)
valgrind 是目前最靠谱的动态检测工具,不用改代码,但只支持 x86/x64,macOS 12+ 需用 valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./your_program(注意:Apple Silicon 上原生不支持,得用 Rosetta 启动)。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 编译时加
-g,否则 valgrind 只能显示汇编地址,看不到函数名和行号 - 避免优化等级过高(比如
-O2),-O0 -g最稳妥 - 关注输出里
at 0x...: your_function (file.cpp:42)这类带源码位置的行,优先修这些 - 如果看到
still reachable,多数是全局缓存或日志缓冲区,不一定算 bug,但得人工确认是否预期
RAII 是防泄漏的底层逻辑,不是语法糖
手动配对 new/delete 在复杂控制流(比如中间有 return、异常抛出)里极易出错。RAII 的核心是“资源即对象”:把内存绑定到栈对象生命周期上,靠析构函数自动释放。
该用什么,怎么选:
- 单个对象 → 用
std::unique_ptr:auto p = std::make_unique<int>(42);</int>,离开作用域自动delete - 需要共享所有权 → 用
std::shared_ptr,但警惕循环引用:std::shared_ptr持有对方,std::weak_ptr观察对方 - 数组 → 必须用
std::unique_ptr<t></t>或std::vector,std::unique_ptr<int></int>配new int[10]会只调delete不调delete[],UB - 别再裸写
new—— 除非你在写底层容器或性能敏感的内联热路径,且能 100% 控制所有出口
ASan(AddressSanitizer)比 valgrind 更快,但只能捕获部分泄漏
ASan 是编译器内置检测器(GCC/Clang 支持),启动快、开销小,但它默认只报内存错误(越界、UAF),泄漏检测需额外开启:-fsanitize=address,leak,且只对 malloc/new 系列有效,不覆盖 mmap 或自定义分配器。
使用场景和限制:
- 开发机日常编译加
-fsanitize=address,leak -g,跑单元测试能快速发现新引入的泄漏 - ASan 报的
LeakSanitizer: detected memory leaks会给出调用栈,但不如 valgrind 全面(比如不报still reachable) - Windows 下用
Application Verifier+Dr. Memory替代,但体验和精度打折扣 - CI 流水线里建议 valgrind 和 ASan 都跑,互补盲区
真正难的不是找到泄漏,而是判断“这块内存该不该释放”——比如一个全局 std::map 缓存,生命周期贯穿整个进程,valgrind 会报它 leaked,但其实是设计如此。这时候得翻文档、问作者、看初始化逻辑,而不是无脑加 delete。










