Valgrind 能直接查 C++ new/delete 泄漏,但需显式启用 --leak-check=full 才扫描未释放堆块;它监控 operator new/malloc 底层分配,不跟踪 RAII 生命周期,依赖 -g 编译和未优化代码定位具体行号。

Valgrind 能不能直接查 C++ 的 new/delete 泄漏
能,但默认不报——valgrind --leak-check=full 才会扫描堆上未释放的 new 块。C++ 的 operator new 和 malloc 都走底层分配器,Valgrind 一并监控;但它**不跟踪 RAII 对象生命周期**,只看原始内存块是否被 delete 或 free 过。
常见误判场景:
- 全局/静态对象的
new内存,程序退出时才析构(Valgrind 会标为 “still reachable”,不是泄漏) - 用
std::vector或std::string等容器,内部 new 的内存由容器自己管理,只要容器析构了就不会报漏 - 忘记
delete[]数组、或用delete错配new[],Valgrind 通常不报内存泄漏,但会报Invalid read/write或Mismatched free() / delete / delete []
必须加的参数和编译选项
没符号信息,Valgrind 只能显示汇编地址;没调试信息,根本没法定位到哪行 new 没配 delete。所以编译时务必:
- 加
-g:生成调试符号 - 避免加
-O2以上优化:内联、死代码消除会让调用栈失真,-O1或-O0更可靠 - 链接时不要 strip:否则
--leak-check=full输出里看不到函数名
运行命令示例:
立即学习“C++免费学习笔记(深入)”;
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose ./my_program
其中 --track-origins=yes 对查 “use-after-free” 很关键,但对纯泄漏意义不大;--show-leak-kinds=all 会把 definitely lost、indirectly lost、possibly lost、still reachable 全列出来——重点关注前两类。
怎么区分 definitely lost 和 still reachable
definitely lost 是最危险的:指堆块指针已丢失(比如局部指针变量出作用域、或指针被覆盖),再无可能释放;still reachable 是指程序结束时仍有指针指向该内存(如全局指针、static 局部变量),理论上还能释放,只是没机会执行了。
典型 still reachable 场景:
-
static std::string s = "hello";—— 构造在程序启动时,析构在退出时,Valgrind 在 main 返回后立即检查,此时析构函数还没跑 - 单例对象内部
new的资源,析构函数注册在atexit,Valgrind 不等它执行就收工
这类不算 bug,但如果你希望 Valgrind 完全静默,得手动在退出前显式清理(比如单例提供 destroy() 并调用),或用 --suppressions=mysupp.supp 抑制特定报告。
为什么跑了 Valgrind 却没看到泄漏报告
最常见三个原因:
- 程序没真正退出:比如用了
while(true) { ... }或阻塞在std::cin,Valgrind 只在进程 exit 时做最终扫描 - 忘了加
--leak-check=full:默认是summary,只输出统计数字,不列具体块 - C++ 异常中途跳出、没走完析构路径:比如
new后抛异常,且没用 RAII 封装,就会变成definitely lost;但若异常被 catch 且手动delete了,就没事
建议在关键路径末尾加 std::exit(0) 强制退出,确保 Valgrind 能扫到;也别依赖 IDE 的“运行”按钮直接跑 Valgrind——有些集成环境会拦截 exit,改用终端执行更稳。
真实项目里,泄漏往往藏在异常分支或条件编译路径里,光看主流程不够;Valgrind 报出来的地址,一定要结合 -g 编译后的符号反查,不然等于白跑。


















