使用valgrind检查内存问题

本文介绍如何使用Valgrind工具检测程序中的内存问题,包括内存泄漏、越界访问、资源泄露等多种常见错误。通过示例代码展示了Valgrind的强大功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用valgrind检查内存问题  2011-12-03 23:56:21

分类: LINUX

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================

工欲善其事,必先利其器。Valgrind作为一个免费且优秀的工具包,平时大部分人可能都是使用valgrind检测内存问题,如内存泄露,越界等。其实Valgrind的用途远不止于此,其实际上为一个工具包,除了检查内存问题以外,还有其它多项用途。我准备将其大致介绍一下。本不想再介绍Valgrind检测内存问题的用法的,但是又一想,毕竟这是Valgrind的一个最有名的用途,如果少了它,不免有些遗憾,所以还是把检查内存问题作为第一篇吧。

请看一下代码:
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>

  4. static void mem_leak1(void)
  5. {
  6.     char *= malloc(1);
  7. }

  8. static void mem_leak2(void)
  9. {
  10.     FILE *fp = fopen("test.txt", "w");
  11. }

  12. static void mem_overrun1(void)
  13. {
  14.     char *= malloc(1);
  15.     *(short*)= 2;

  16.     free(p);
  17. }

  18. static void mem_overrun2(void)
  19. {
  20.     char array[5];
  21.     strcpy(array, "hello");
  22. }

  23. static void mem_double_free(void)
  24. {
  25.     char *= malloc(1);
  26.     free(p);
  27.     free(p);
  28. }

  29. static void mem_use_wild_pointer(void)
  30. {
  31.     char *= (void*)0x80184800;
  32.     *= 1;
  33. }

  34. static void mem_free_wild_pointer(void)
  35. {
  36.     char *p;
  37.     free(p);
  38. }


  39. int main()
  40. {
  41.     mem_leak1();
  42.     mem_leak2();
  43.     mem_overrun1();
  44.     mem_overrun2();
  45.     mem_double_free();
  46.     //mem_use_wild_pointer();
  47.     mem_free_wild_pointer();

  48.     return 0;
  49. }
这里一共列出了七种常见的内存问题:1. 动态内存泄露;2. 资源泄露,这里以文件描述符为例;3. 动态内存越界;4.数组内存越界;5.动态内存double free;6.使用野指针,即未初始化的指针;7.释放野指针,即未初始化的指针;其中由于本示例代码过于简单,第6中情况,使用野指针会直接导致crash,所以在main中,并没有真正的调用那个示例代码。由于valgrind只能检测执行到的代码,所以在后面的报告中,不会报告第6种错误情况。但是,在大型的项目中,有可能使用野指针并不会导致程序crash。另外上面的7中情况,有些情况严格的说,实际上可以归为一类。

下面看怎样执行valgrind来检测内存错误:
  1. valgrind --track-fds=yes --leak-check=full --undef-value-errors=yes ./a.out
上面那些option的具体含义,可以参加valgrind --help,其中有些option默认就是打开的,不过我习惯于明确的使用option,以示清晰。

看执行后的报告:
  1. ==2326== Memcheck, a memory error detector
  2. ==2326== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
  3. ==2326== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
  4. ==2326== Command: ./a.out
  5. ==2326==
  6. /* 这里检测到了动态内存的越界,提示Invalid write。*/
  7. ==2326== Invalid write of size 2
  8. ==2326== at 0x80484B4: mem_overrun1 (in /home/fgao/works/test/a.out)
  9. ==2326== by 0x8048553: main (in /home/fgao/works/test/a.out)
  10. ==2326== Address 0x40211f0 is 0 bytes inside a block of size 1 alloc'd
  11. ==2326== at 0x4005BDC: malloc (vg_replace_malloc.c:195)
  12. ==2326== by 0x80484AD: mem_overrun1 (in /home/fgao/works/test/a.out)
  13. ==2326== by 0x8048553: main (in /home/fgao/works/test/a.out)
  14. ==2326==
 /* 这里检测到了double free问题,提示Invalid Free  */
  1. ==2326== Invalid free() / delete / delete[]
  2. ==2326== at 0x40057F6: free (vg_replace_malloc.c:325)
  3. ==2326== by 0x8048514: mem_double_free (in /home/fgao/works/test/a.out)
  4. ==2326== by 0x804855D: main (in /home/fgao/works/test/a.out)
  5. ==2326== Address 0x4021228 is 0 bytes inside a block of size 1 free'd
  6. ==2326== at 0x40057F6: free (vg_replace_malloc.c:325)
  7. ==2326== by 0x8048509: mem_double_free (in /home/fgao/works/test/a.out)
  8. ==2326== by 0x804855D: main (in /home/fgao/works/test/a.out)
  9. ==2326==
  10. /* 这里检测到了未初始化变量 */
  11. ==2326== Conditional jump or move depends on uninitialised value(s)
  12. ==2326== at 0x40057B6: free (vg_replace_malloc.c:325)
  13. ==2326== by 0x804853C: mem_free_wild_pointer (in /home/fgao/works/test/a.out)
  14. ==2326== by 0x8048562: main (in /home/fgao/works/test/a.out)
  15. ==2326==
 /* 这里检测到了非法是否野指针 */
  1. ==2326== Invalid free() / delete / delete[]
  2. ==2326== at 0x40057F6: free (vg_replace_malloc.c:325)
  3. ==2326== by 0x804853C: mem_free_wild_pointer (in /home/fgao/works/test/a.out)
  4. ==2326== by 0x8048562: main (in /home/fgao/works/test/a.out)
  5. ==2326== Address 0x4021228 is 0 bytes inside a block of size 1 free'd
  6. ==2326== at 0x40057F6: free (vg_replace_malloc.c:325)
  7. ==2326== by 0x8048509: mem_double_free (in /home/fgao/works/test/a.out)
  8. ==2326== by 0x804855D: main (in /home/fgao/works/test/a.out)
  9. ==2326==
  10. ==2326==
  11. /* 
  12. 这里检测到了文件指针资源的泄露,下面提示说有4个文件描述符在退出时仍是打开的。
  13. 描述符0,1,2无需关心,通过报告,可以发现程序中自己明确打开的文件描述符没有关闭。
  14. */
  15. ==2326== FILE DESCRIPTORS: 4 open at exit.
  16. ==2326== Open file descriptor 3: test.txt
  17. ==2326== at 0x68D613: __open_nocancel (in /lib/libc-2.12.so)
  18. ==2326== by 0x61F8EC: __fopen_internal (in /lib/libc-2.12.so)
  19. ==2326== by 0x61F94B: fopen@@GLIBC_2.1 (in /lib/libc-2.12.so)
  20. ==2326== by 0x8048496: mem_leak2 (in /home/fgao/works/test/a.out)
  21. ==2326== by 0x804854E: main (in /home/fgao/works/test/a.out)
  22. ==2326==
  23. ==2326== Open file descriptor 2: /dev/pts/4
  24. ==2326== <inherited from parent>
  25. ==2326==
  26. ==2326== Open file descriptor 1: /dev/pts/4
  27. ==2326== <inherited from parent>
  28. ==2326==
  29. ==2326== Open file descriptor 0: /dev/pts/4
  30. ==2326== <inherited from parent>
  31. ==2326==
  32. ==2326==
  33. /* 堆信息的总结:一共调用4次alloc,4次free。之所以正好相等,因为上面有一函数少了free,有一个函数多了一个free */
  34. ==2326== HEAP SUMMARY:
  35. ==2326== in use at exit: 353 bytes in 2 blocks
  36. ==2326== total heap usage: 4 allocs, 4 frees, 355 bytes allocated
  37. ==2326==
  38. /* 检测到一个字节的内存泄露 */
  39. ==2326== 1 bytes in 1 blocks are definitely lost in loss record 1 of 2
  40. ==2326== at 0x4005BDC: malloc (vg_replace_malloc.c:195)
  41. ==2326== by 0x8048475: mem_leak1 (in /home/fgao/works/test/a.out)
  42. ==2326== by 0x8048549: main (in /home/fgao/works/test/a.out)
  43. ==2326==
  44. /* 内存泄露的总结 */
  45. ==2326== LEAK SUMMARY:
  46. ==2326== definitely lost: 1 bytes in 1 blocks
  47. ==2326== indirectly lost: 0 bytes in 0 blocks
  48. ==2326== possibly lost: 0 bytes in 0 blocks
  49. ==2326== still reachable: 352 bytes in 1 blocks
  50. ==2326== suppressed: 0 bytes in 0 blocks
  51. ==2326== Reachable blocks (those to which a pointer was found) are not shown.
  52. ==2326== To see them, rerun with: --leak-check=full --show-reachable=yes
  53. ==2326==
  54. ==2326== For counts of detected and suppressed errors, rerun with: -v
  55. ==2326== Use --track-origins=yes to see where uninitialised values come from
  56. ==2326== ERROR SUMMARY: 5 errors from 5 contexts (suppressed: 12 from 8)

这个只是一个简单的示例程序,即使没有Valgrind,我们也可以很轻易的发现问题。但是在真实的项目中,当代码量达到万行,十万行,甚至百万行时。由于申请的内存可能不是在一个地方使用,不可避免的被传来传去。这时,如果光是看review代码来检查问题,可能很难找到根本原因。这时,使用Valgrind则可以很容易的发现问题所在。

当然,Valgrind也不是万能的。我也遇到过Valgrind无法找到问题,反而我通过不断的review代码找到了症结。发现问题,解决问题,毕竟是末流。最好的方法,就是不引入内存问题。这可以通过良好的代码风格和设计来实现的。

写代码不是那么容易的。要用心,把代码当作自己的作品,真心的去写好它。这样,自然而然的就会把代码写好。
<think>我们使用Valgrind来检测C程序中的内存泄漏。Valgrind是一个强大的内存调试工具,能够检测内存泄漏、非法内存访问、使用未初始化内存问题。以下是详细步骤: ### 步骤1: 编译程序时添加调试信息 在编译C程序时,必须使用`-g`选项,这样Valgrind才能显示具体的代码行号。 ```bash gcc -g -o my_program my_program.c ``` ### 步骤2: 使用Valgrind运行程序 使用以下命令运行程序并检测内存泄漏: ```bash valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --log-file=valgrind_log.txt ./my_program ``` - `--leak-check=full`: 详细显示每个内存泄漏的位置。 - `--show-leak-kinds=all`: 显示所有类型的内存泄漏(包括明确泄漏和可能泄漏)。 - `--track-origins=yes`: 追踪未初始化值的来源,有助于发现未初始化变量的问题。 - `--log-file=valgrind_log.txt`: 将输出重定向到文件,方便查看。 ### 步骤3: 分析Valgrind输出 运行结束后,打开日志文件`valgrind_log.txt`查看结果。重点关注以下部分: #### 内存泄漏报告示例 ``` ==12345== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==12345== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==12345== by 0x10865B: main (my_program.c:10) ``` - **definitely lost**: 明确的内存泄漏,程序未释放分配的内存。 - **间接泄漏**: 指向泄漏内存的指针位于另一个泄漏的内存块中。 - **可能泄漏**: 可能存在内存泄漏(例如,当指向内存的指针位于已释放的内存块中时)。 #### 错误类型说明 - **Invalid read/write**: 非法内存访问(如越界访问)。 - **Use of uninitialised value**: 使用了未初始化的变量。 - **Conditional jump or move depends on uninitialised value(s)**: 条件判断依赖于未初始化的值。 ### 步骤4: 修复内存问题 根据Valgrind报告中的文件名和行号,定位代码问题并修复。常见修复包括: 1. **释放动态分配的内存**: ```c // 修复前 int *arr = malloc(10 * sizeof(int)); // ... 使用arr // 忘记free(arr) // 修复后 free(arr); // 添加释放 ``` 2. **避免重复释放**: ```c free(ptr); // ... free(ptr); // 错误:重复释放 ``` 3. **检查数组越界**: ```c int arr[10]; for (int i=0; i<=10; i++) { // 越界访问arr[10] arr[i] = i; } ``` ### 高级技巧:忽略某些库的误报 若Valgrind报告系统库的内存问题(如libc),可使用`--suppressions`选项忽略: ```bash valgrind --suppressions=/path/to/suppress.txt ./my_program ``` 其中`suppress.txt`包含需要忽略的错误模式。 ### 注意事项 1. **程序运行时间**:Valgrind会使程序运行速度降低10-50倍,对于长时间运行的程序需耐心等待。 2. **多线程程序**:Valgrind支持多线程检测,但需确保程序线程安全[^2]。 3. **退出状态**:Valgrind返回非0值表示检测到错误,可用于自动化测试。 ### 示例:检测并修复内存泄漏 假设有以下存在内存泄漏的程序`leak.c`: ```c #include <stdlib.h> int main() { int *p = malloc(10 * sizeof(int)); // 未释放 return 0; } ``` 使用Valgrind检测: ```bash gcc -g -o leak leak.c valgrind --leak-check=full ./leak ``` 输出报告将指出: ``` HEAP SUMMARY: in use at exit: 40 bytes in 1 blocks total heap usage: 1 allocs, 0 frees, 40 bytes allocated 40 bytes definitely lost: at 0x4C2FB0F: malloc (vg_replace_malloc.c:299) by 0x10865B: main (leak.c:4) ``` 修复后(添加`free(p)`): ```c #include <stdlib.h> int main() { int *p = malloc(10 * sizeof(int)); free(p); // 修复:释放内存 return 0; } ``` ### 相关问题 1. Valgrind能检测哪些类型的内存错误? 2. 如何优化程序以减少内存泄漏的风险? 3. Valgrind在多线程环境下的使用有什么注意事项? 4. 除了Valgrind,还有哪些工具可以检测C程序的内存问题?</think>### 如何使用 Valgrind 检测 C 程序中的内存泄漏 Valgrind 是 C/C++ 开发中必备的内存调试工具,能精确检测内存泄漏、非法访问等问题。以下是详细使用指南: --- ### 一、安装 Valgrind ```bash # Ubuntu/Debian sudo apt-get install valgrind # CentOS/RHEL sudo yum install valgrind # macOS (需 Homebrew) brew install valgrind ``` --- ### 二、编译程序(关键步骤) **必须添加 `-g` 编译选项**,保留调试信息: ```bash gcc -g -o my_program my_program.c ``` --- ### 三、基础检测命令 ```bash valgrind --leak-check=full --show-leak-kinds=all ./my_program ``` - `--leak-check=full`:显示详细的泄漏位置 - `--show-leak-kinds=all`:显示所有泄漏类型 --- ### 四、高级检测命令(推荐) ```bash valgrind --tool=memcheck \ --leak-check=full \ --track-origins=yes \ --log-file=valgrind_report.txt \ ./my_program ``` - `--track-origins=yes`:追踪未初始化变量的来源 - `--log-file`:输出到文件(避免终端截断) --- ### 五、解读检测报告 报告关键部分解析: ```log ==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 2 ==12345== at 0x483B7F3: malloc (vg_replace_malloc.c:307) ==12345== by 0x10916E: main (example.c:10) <-- 泄漏位置 ``` - **泄漏类型**: - `definitely lost`:明确泄漏(未释放) - `indirectly lost`:间接泄漏(父结构未释放) - `possibly lost`:指针指向内存中间 - `still reachable`:程序退出时仍可访问(需检查是否应释放) --- ### 六、实战修复示例 #### 问题代码: ```c // leak.c #include <stdlib.h> int main() { int *p = malloc(10 * sizeof(int)); // 未释放 return 0; } ``` #### 检测步骤: 1. 编译:`gcc -g -o leak leak.c` 2. 运行检测:`valgrind --leak-check=full ./leak` 3. 报告片段: ```log ==31542== 40 bytes in 1 blocks are definitely lost ==31542== at 0x483B7F3: malloc (vg_replace_malloc.c:307) ==31542== by 0x10915A: main (leak.c:4) <-- 泄漏在第4行 ``` #### 修复方案: ```c int main() { int *p = malloc(10 * sizeof(int)); free(p); // 添加释放 return 0; } ``` --- ### 七、特殊场景处理 1. **忽略系统库误报**: ```bash valgrind --suppressions=/path/to/suppress.txt ./my_program ``` 在 `suppress.txt` 中添加: ```log { <libc_suppression> Memcheck:Leak fun:malloc ... } ``` 2. **检测文件描述符泄漏**: ```bash valgrind --track-fds=yes ./my_program ``` 3. **检测多线程问题**: ```bash valgrind --tool=helgrind ./my_program ``` --- ### 八、最佳实践 1. **持续集成集成**:在 CI 流程中加入 Valgrind 检查 2. **测试覆盖**:确保测试用例覆盖所有分配路径 3. **日志分析**:使用脚本解析报告([示例脚本](https://github.com/LouisBrunner/valgrind-macos)) 4. **结合 GDB**:对复杂问题使用 `valgrind --vgdb=yes` > 经验:每次 `malloc/calloc` 后立即规划释放位置,能预防 90% 的内存泄漏[^1][^2]。 --- ### 常见问题解决 - **`Valgrind: FATAL: can&#39;t execute`** → 检查程序编译是否添加 `-g` - **报告不显示行号** → 确保未使用优化标志(如 `-O2`) - **检测大型程序卡顿** → 使用 `--fair-sched=yes` 改进多线程调度 --- ### 内存泄漏类型速查表 | 泄漏类型 | 严重程度 | 典型原因 | |------------------|----------|--------------------------| | definitely lost | ⚠️⚠️⚠️ | malloc 后未 free | | indirectly lost | ⚠️⚠️ | 结构体内存未完全释放 | | possibly lost | ⚠️ | 指针越界后丢失原始地址 | | still reachable | ⚠️ | 全局变量未释放 | ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值