valgrind和addresssanitizer是定位内存泄漏的核心工具:valgrind适用于linux,需-g -o0编译并运行--leak-check=full;macos catalina后推荐asan,加-fsanitize=address并设asan_options=detect_leaks=1;智能指针不能杜绝泄漏,需警惕循环引用、裸指针逃逸、删除器缺失等问题;文件句柄等系统资源泄漏同样致命,应结合raii封装与lsof/strace排查。

用 valgrind 快速定位内存泄漏(Linux/macOS)
Linux 或 macOS 下,valgrind 是最直接有效的运行时检测工具,它不依赖代码修改,能精准指出哪行 malloc 或 new 没被配对释放。
常见错误现象:valgrind 报告 “definitely lost” 或 “still reachable”,但你没看到明显漏删的 delete —— 往往是异常路径绕过了清理逻辑,或智能指针没接管原始指针。
- 编译时加
-g(否则看不到源码行号),禁用优化(-O0),否则行号错乱、内联干扰检测 - 运行命令:
valgrind --leak-check=full --show-leak-kinds=all ./your_program - 重点关注 “by 0x...: your_function (file.cpp:42)” 这类调用栈,不是只看最后一行
- 注意:macOS Catalina 及以后默认不支持
valgrind,需用llvm's AddressSanitizer替代
AddressSanitizer(ASan)在 GCC/Clang 中启用内存泄漏检查
AddressSanitizer 不仅查越界,加一个编译选项就能捕获内存泄漏,且支持 Windows(MSVC)、macOS、Linux,比 valgrind 更现代、更快。
使用场景:CI 流水线集成、开发机日常构建、跨平台项目统一检测。
立即学习“C++免费学习笔记(深入)”;
- Clang/GCC 编译加参数:
-fsanitize=address -fno-omit-frame-pointer -g - 运行前必须设置:
export ASAN_OPTIONS=detect_leaks=1(Linux/macOS);Windows 上用set ASAN_OPTIONS=detect_leaks=1 - 泄漏报告里会显示分配点(
malloc/new行)和未释放原因(如全局指针持有、循环引用) - 注意:链接静态库时,所有目标文件都得用 ASan 编译,否则漏检;C++ 异常抛出路径中的资源释放容易被忽略,ASan 能暴露这类问题
智能指针不能自动解决所有泄漏——哪些情况会失效
std::unique_ptr 和 std::shared_ptr 确实大幅降低手动管理风险,但它们不是银弹。很多“看似用了智能指针”的代码仍泄漏,根源在语义误用。
常见错误现象:程序退出后 valgrind 或 ASan 仍报泄漏,但代码里全是 std::shared_ptr。
-
std::shared_ptr循环引用:A 持有 B 的shared_ptr,B 也持有 A 的shared_ptr→ 引用计数永不归零 → 析构不触发 - 裸指针逃逸:用
get()获取原始指针并传给 C 风格 API(如pthread_create),之后忘记回收,智能指针以为自己管完了 - 自定义删除器缺失或错误:比如用
shared_ptr管理malloc内存,却没传free当删除器 → 调用默认delete→ UB + 泄漏 - 全局/静态
shared_ptr:若初始化顺序不当或析构顺序混乱,可能在 DLL 卸载或 main 返回后才尝试释放,被工具误判为泄漏
RAII 外的“隐式资源”:文件句柄、socket、GPU 显存也属于内存泄漏范畴
严格来说,C++ 标准库不管理这些,但系统资源耗尽的表现和堆内存泄漏完全一致:程序变慢、打开失败、errno = EMFILE 等。它们常被忽略,因为不走 new/delete 路径。
使用场景:网络服务、嵌入式、图形程序中频繁创建 socket、FILE*、OpenGL/Vulkan 对象。
- 别只盯着
new—— 检查所有fopen、socket、glGenBuffers是否有对应fclose、close、glDeleteBuffers - 用 RAII 封装:写个
FileGuard类,在构造里fopen,析构里fclose;不要依赖“后面会统一关”的注释 - 工具层面:
lsof -p PID查进程打开的文件描述符数量;strace -e trace=open,close,socket,close ./prog看系统调用是否配对 - 注意:某些平台(如旧版 Android)对 socket 描述符限制极严,1024 个上限下漏 3 个就卡死,比堆泄漏更早暴露
真正难排查的从来不是“忘了 delete”,而是资源生命周期和控制流不匹配——比如异常跳过 cleanup、多线程竞争释放、或把资源绑定到错误的作用域。工具只能告诉你“哪儿没还”,但“为什么没还”得靠你读调用栈和控制流图。








