Valgrind memcheck:查内存泄漏与越界
更新时间:2026-09-19。memcheck 是 Valgrind 最常用的工具,能查出 C/C++ 程序的内存泄漏、非法读写、使用未初始化值、重复释放等问题。它是动态二进制插桩,不需要改代码,但会带来约 10~50 倍减速。
一、准备与基本命令
bash
# 1) 用 -g 编译保留调试信息,-O0/-O1 便于定位(不要 strip)
gcc -g -O0 -o prog main.c
# 2) 最常用的完整检查
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--error-limit=no \
./prog arg1 arg2| 选项 | 作用 |
|---|---|
--leak-check=full | 退出时给出每个泄漏的完整调用栈 |
--show-leak-kinds=all | 显示 definite/indirect/possible/reachable 全部类型 |
--track-origins=yes | 追踪未初始化值的来源(多花开销,定位极有用) |
--error-limit=no | 不限制错误条数 |
--log-file=v.log | 输出写文件 |
二、结果怎么读
bash
==12345== Invalid read of size 4 # 越界读(堆/栈/已释放内存)
==12345== at 0x... foo (main.c:20)
==12345== by 0x... main (main.c:40)
==12345== HEAP SUMMARY:
==12345== in use at exit: 1,024 bytes in 4 blocks
==12345== definitely lost: 256 bytes in 1 blocks # 真泄漏:没人指向,无法再释放
==12345== indirectly lost: 512 bytes in 2 blocks # 因根对象泄漏而连带的(如链表节点)
==12345== possibly lost: 0 bytes # 指针指向内部、疑似泄漏
==12345== still reachable: 256 bytes # 退出时仍有指针(全局缓存/未 free)| 分类 | 含义 | 处理 |
|---|---|---|
| definitely lost | 确定泄漏,内存再无引用 | 必须修 |
| indirectly lost | 因 definite 泄漏的结构连带(节点成员) | 修了根就消失 |
| possibly lost | 指针指向块中部(常见于偏移指针/字符串) | 确认是否合理 |
| still reachable | 退出时仍能从全局变量到达 | 一般无害(缓存/单例),严谨程序也清零 |
Invalid read/write 是比泄漏更危险的内存破坏(可能已踩坏别的数据);Syscall ... contains uninitialised byte、Conditional jump depends on uninitialised value 说明用了没初始化的变量,开 --track-origins=yes 能追到源头。
三、典型问题与修法
c
int *p = malloc(4 * sizeof(int));
p[4] = 1; // 越界写 → Invalid write of size 4(下标 0..3)
int x;
if (x == 0) {} // 未初始化 → Conditional jump ... uninitialised
free(p); free(p); // 重复释放 → Invalid free()修法对应:严格边界检查、变量初始化、配对分配释放(或用 RAII/智能指针)。泄漏只报告一次,要结合调用栈频率判断影响;长跑服务可在关键阶段比较两次 heap summary 的 in-use 增长。
四、适用边界与替代
- memcheck 慢(10~50 倍)、内存翻倍,适合功能/测试环境,不适合压测和生产常态。
- 对短生命周期、需要性能的回归,用 AddressSanitizer(-fsanitize=address) 和 LeakSanitizer:2~3 倍开销、更快,能定位越界/泄漏;
-fsanitize=undefined查未定义行为。 - Valgrind 强项是不重编译即可精细检查和丰富的生态(cache 分析见下一篇),ASan 适合纳入 CI。
- JIT/自修改代码、部分 SIMD/老内核对 Valgrind 支持有限。
坑
- 发布版 strip 掉符号、用高优化级别,调用栈全是地址无法定位;务必 -g -O0/1。
- 把 still reachable 全当严重泄漏去修,浪费精力(先看 definitely)。
- 在生产高负载跑 memcheck,服务被拖垮。
- 忽略 invalid read/write——它比泄漏更可能造成隐蔽数据损坏/安全漏洞。
- 只在正常路径跑一次,错误处理/异常分支的泄漏没覆盖到。
总结
用 Valgrind memcheck 时先用 -g -O0 编译,再以 --leak-check=full --show-leak-kinds=all --track-origins=yes 运行,重点关注 definitely lost 这类确定泄漏、Invalid read/write 越界和依赖未初始化值的分支,indirectly lost 会随根泄漏修复消失、still reachable 多为全局缓存;由于 10~50 倍的运行开销它适合测试环境精细排查,需要纳入 CI 或高性能场景则用 AddressSanitizer/LeakSanitizer 替代。