Appearance
[English version](/posts/c教程/en-US/08-Debugging Testing Profiling/072-Using Valgrind for Memory Checking)
如果 gdb 可以帮助您了解程序如何运行,那么 Valgrind 可以帮助您了解程序的泄漏位置。 C 为您提供了对内存的原始控制,这意味着您负责每次分配、释放和指针访问。
当您需要找到以下内容时,Valgrind 是您最好的朋友:
- 内存泄漏(忘记
free()) - 无效读/写
- 双重免费
- 释放后使用错误
让我们学习如何使用它来使您的程序可靠且无泄漏。
步骤1.安装Valgrind
在 Linux 上:
sudo apt install valgrind在 macOS 上(使用 Homebrew):
brew install valgrind使用调试符号编译程序:
gcc -g memory.c -o memory步骤 2. 使用 Valgrind 运行
在 Valgrind 下运行你的程序:
valgrind ./memoryValgrind 在虚拟 CPU 中运行您的程序并监视每个内存操作。最后,它打印分配和泄漏的详细报告。
步骤 3. 一个简单的例子
这是一个有两个常见错误的程序:泄漏和无效释放。
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *p = malloc(10 * sizeof(int));
p[10] = 42; // invalid write (out of bounds)
return 0; // forgot to free(p)
}运行它:
gcc -g mem_bug.c -o mem_bug
valgrind ./mem_bug输出:
==1234== Invalid write of size 4
==1234== at 0x1091A: main (mem_bug.c:6)
==1234== Address 0x5201048 is 0 bytes after a block of size 40 alloc'd
==1234== at 0x484186F: malloc (vg_replace_malloc.c:380)
==1234==
==1234== HEAP SUMMARY:
==1234== definitely lost: 40 bytes in 1 blocks
==1234== LEAK SUMMARY:
==1234== definitely lost: 40 bytes in 1 blocksValgrind 捕获了无效访问和泄漏。
步骤 4. 修复错误
正确版本:
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *p = malloc(10 * sizeof(int));
if (!p) return 1;
p[9] = 42; // valid index
free(p);
}再次运行:
valgrind ./mem_bug输出:
== All heap blocks were freed -- no leaks are possible
== ERROR SUMMARY: 0 errors from 0 contexts干净又完美。
步骤 5. 检测释放后使用
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *p = malloc(sizeof(int));
*p = 5;
free(p);
printf("%d\n", *p); // using freed memory
}瓦尔格林德 说:
==1234== Invalid read of size 4
==1234== at 0x1091A: main (use_after_free.c:7)
==1234== Address 0x5201040 is 0 bytes inside a block of size 4 free'd它甚至显示了块被释放的位置。
步骤 6. 检测双重释放
#include <stdlib.h>
int main(void) {
int *p = malloc(4);
free(p);
free(p);
}Valgrind 输出:
==1234== Invalid free() / delete / delete[]
==1234== at 0x4845DEF: free (vg_replace_malloc.c:872)
==1234== Address 0x5201040 was freed previously步骤 7. 内存泄漏类别
Valgrind 将泄漏分为几类:
| 类型 | 意义 |
|---|---|
| 肯定输了 | 没有指向该块的指针保留,真正的泄漏 |
| 间接损失 | 被泄露的块引用 |
| 可能丢失 | 指针可能存在但Valgrind无法确认 |
| 仍然可达 | 程序结束但内存未释放(通常是无害的) |
步骤 8. 获取干净的报告
仅检查是否存在真正的泄漏:
valgrind --leak-check=full --show-leak-kinds=definite ./program对于更详细的跟踪:
valgrind --track-origins=yes ./program该标志告诉您未初始化的变量首次出现的位置。
步骤 9. 检查堆栈或未初始化的值
#include <stdio.h>
int main(void) {
int x;
printf("%d\n", x); // uninitialized read
}Valgrind 输出:
==1234== Use of uninitialised value of size 4
==1234== at 0x1091A: main (uninit.c:5)始终初始化变量!
步骤 10. 小代码:泄漏检测器
#include <stdlib.h>
void leak1(void) { malloc(100); }
void leak2(void) { char *p = malloc(50); free(p); }
int main(void) {
leak1();
leak2();
}跑步:
valgrind --leak-check=full ./leaks输出:
==1234== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==1234== LEAK SUMMARY:
==1234== definitely lost: 100 bytes in 1 blocks仅有的leak1()忘记了free(),Valgrind 准确地指出了这一点。
为什么它很重要
Valgrind 可以帮助您:
- 查找隐藏的内存泄漏
- 检测无效指针的使用
- 捕获未初始化的值
- 编写更安全、更干净、更可靠的C
它是工作流程中必不可少的工具,特别是对于长时间运行的程序、服务器或系统软件。
自己尝试一下
编写一个分配多个块但忘记释放其中一个的程序。
故意使用p[10]在一个malloc(10)堵塞。
触发释放后使用并在 Valgrind 中找到它。
使用--track-origins=yes跟踪未初始化的数据。
重构您的代码,直到 Valgrind 报告:
All heap blocks were freed -- no leaks are possible接下来,您将探索断言和防御性编程,这些技术可以在逻辑错误达到运行时崩溃之前捕获它们。
