编译需加-g且禁用优化(-O0),Valgrind须用--leak-check=full,报告中最后一行“by”指向泄漏源头,应存日志筛选definitely/indirectly lost。

编译时必须加 -g
Valgrind 默认只显示地址和汇编偏移,不带调试信息就看不到函数名和行号。核心前提是:源码编译时得带上 -g,否则 Valgrind 拿不到符号表。
常见错误是直接用 gcc -o app app.c 编译后就跑 Valgrind,结果输出里全是 0x401156 这类地址,没法定位。
- 正确做法:
gcc -g -O0 -Wall app.c -o app -
-O0很关键——优化开启后(比如-O2)会导致行号错位、函数内联、栈帧消失,Valgrind 报的行号可能指向错误位置 - 如果用 CMake,确保
CMAKE_BUILD_TYPE是Debug,它会自动加-g和-O0
valgrind --leak-check=full 才触发完整调用栈
只用 valgrind ./app 会走默认 Memcheck,但泄漏检测级别低,往往只报 still reachable,不展开分配点。要看到 main (main.c:15) 这种格式,必须显式启用完整泄漏检查。
- 命令必须包含
--leak-check=full,例如:valgrind --leak-check=full ./app - 可以合并写成:
valgrind --tool=memcheck --leak-check=full ./app(--tool=memcheck其实可省略,但显式写出更清晰) - 如果程序有参数,参数直接跟在可执行文件后面:
valgrind --leak-check=full ./app --input data.bin
输出里找 by 0x... 行,不是最上面那几行
Valgrind 的报告是倒着读的:错误根源在调用栈底部,而不是顶部。很多人扫一眼开头的 Invalid write of size 4 就停了,其实关键信息藏在下面几层。
典型泄漏报告片段:
==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==12345== at 0x483B7F3: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==12345== by 0x401156: main (main.c:15)
注意最后一行:by 0x401156: main (main.c:15) —— 这才是泄漏发生的源头。前面几行是 malloc 内部调用路径,不用管。
- 所有带
by的行,从下往上读,最底下的那个by就是你的代码位置 - 如果看到
operator new或std::allocator,说明是 C++ new 分配的,行号仍指向你调用new的那一行 - 第三方库(如 OpenCV)的泄漏常出现在初始化函数里,这时行号可能是库内部,需结合上下文判断是否真属于你的逻辑
大项目务必用 --log-file= 保存输出
终端滚动太快,几十屏报错一闪而过,根本来不及翻。尤其带第三方库时,Valgrind 可能报出上百个 still reachable,真正属于你的泄漏被埋没。
- 加
--log-file=valgrind.log把全部输出存到文件:valgrind --leak-check=full --log-file=valgrind.log ./app - 然后用
grep "by.*:" valgrind.log快速筛选出所有含源码位置的行 - 重点搜
definitely lost和indirectly lost,这两类才是真实泄漏;still reachable多数是全局对象或缓存,一般不用处理
行号能对上,不代表问题一定在那一行——比如 malloc 在第 15 行,但 free 忘在第 89 行释放,Valgrind 只会标出分配点。真正要盯的是“谁该负责释放”这个逻辑链,而不是光看一个数字。


















