--leak-check=full和--show-leak-kinds=all必须同时启用,才能完整暴露definitely、indirectly、possibly lost及still reachable四类泄漏;后者常被误判为安全,实则反映资源生命周期设计缺陷。

用 --leak-check=full 和 --show-leak-kinds=all 同时开启
--leak-check=full 是基础,但只开它还不够。默认情况下 Valgrind 会过滤掉 still reachable 和 indirectly lost 类型的泄漏,而这些在复杂模块(比如 Nginx pool 管理或 C++ RAII 链)中往往藏着关键线索。必须显式加上 --show-leak-kinds=all,才能看到全部四类:
- definitely lost:指针彻底丢失,最危险
- indirectly lost:父块没释放导致子块连带泄漏
- possibly lost:可能因栈/寄存器覆盖丢失引用,需人工确认
- still reachable:退出时仍有指针指向,但未 free —— 这类在长期运行服务中常被误判为“安全”,实则暴露资源生命周期设计缺陷
漏掉其中任何一类,都可能让真实泄漏逃过检测。
加 --track-origins=yes 定位未初始化内存的传播路径
很多泄漏不是直接 malloc 没 free,而是从一个未初始化指针开始,经过多次赋值、传参、结构体嵌套,最终在某处误当成有效指针使用并触发分配。这时 --track-origins=yes 能回溯到最初那个 malloc 或栈变量未初始化的位置。
代价是性能下降 2–3 倍,但对定位“为什么这个指针突然变 NULL 或野指针”极其关键。尤其在 C++ 模板展开、宏定义多层包裹的代码里,不加这个选项,Valgrind 只报“invalid read”,却不说源头在哪。
确保二进制带 -g 且禁用内联优化
没有调试符号,--leak-check=full 输出的调用栈就是一堆 ??? 和地址偏移,毫无意义。必须编译时加 -g,并且避免 -O2 及以上优化 —— 否则函数内联会让栈帧消失,Valgrind 看不到实际调用链。
更隐蔽的坑是 -fomit-frame-pointer(GCC 默认在 x86-64 开启),它会让 Valgrind 无法重建完整调用栈。务必显式加 -fno-omit-frame-pointer,尤其在 Nginx 模块或高性能服务中。
- 正确:
gcc -g -O0 -fno-omit-frame-pointer foo.c -o foo - 错误:
gcc -O2 foo.c -o foo(栈信息残缺) - 错误:
gcc -g -O2 foo.c -o foo(内联 + 优化双重干扰)
输出重定向到文件并启用 --verbose
--verbose 不只是“多打几行日志”。它会显示 Valgrind 自身加载的符号表、拦截的系统调用、线程创建/销毁事件,甚至告诉你哪些共享库没找到 debuginfo —— 这些信息在排查“为什么某次 malloc 没被监控到”时是唯一线索。
同时必须用 --log-file=report.txt 把输出存下来:valgrind 的终端输出常因缓冲或截断丢失关键段落,特别是长调用栈和重复泄漏聚合块。
典型命令组合:
valgrind --tool=memcheck \ --leak-check=full \ --show-leak-kinds=all \ --track-origins=yes \ --verbose \ --log-file=valgrind-report.log \ ./myapp
最常被忽略的是:Valgrind 报告里的“still reachable”不是噪音,而是你程序里全局池、单例缓存、静态容器的真实快照——它们是否该在进程退出前显式清理,取决于你的资源模型,而不是 Valgrind 的分类标签。











