用 valgrind 检查 C++ 程序的内存泄漏

作者: , 共 4153 字

C++内存检查和性能分析工具 valgrind里介绍了 valgrind 的安装,以及用于效率分析 profiler 工具。valgrind 最本来的功能是内存检查。这篇文章做简单的介绍。

1. 内存检查的基本命令

基本命令是:

valgrind [valgrind-paras] your-program [you-program-paras]

常见选项有:

选项 解释
--leak-check=full 检查所有错误信息
--show-leak-kinds=all 显示所有错误详情

valgrind 会输出很多结果。从大类可以分为内存泄漏检查结果和其它内存错误结果。

2. valgrind 检查的内存泄漏结果

2.1. 内存泄漏的四个类型 LEAK SUMMARY

内存泄漏被分为四种:

错误类型 解释
definitely lost 内存泄漏,并且没有变量指向该内存。
indirectly lost 内存泄漏,所有指向该内存的变量已泄漏。indirectly lost 一般预示有 definitely lost 错误,解决后者就解决了前者。
possible lost 内存未被删除,有变量指向内存中间位置。
still reachable 内存未被删除,有全局变量指向内存起始处。

still reachable 跟全局变量有关,它的泄漏是一次性行为。因此大家认为 still reachable 的影响不严重, valgrind 也默认不显示详情。若想查看 still reachable 的错误详情,需增加--show-leak-kinds=all选项。前两类错误则可能在程序运行过程中不断泄漏内存从而引起系统奔溃,尤其是需长时间运行的服务器程序。

valgrind 会给出四类错误的统计数据:

==86262== LEAK SUMMARY:
==86262==    definitely lost: 96 bytes in 12 blocks
==86262==    indirectly lost: 0 bytes in 0 blocks
==86262==      possibly lost: 0 bytes in 0 blocks
==86262==    still reachable: 24,076 bytes in 54 blocks
==86262==         suppressed: 0 bytes in 0 blocks

其中 86262 是进程 id。

2.2. 错误代码示例

#include <memory.h>

struct Link {
    Link* next;
};

Link* still_reachable;
Link* possible_lost;

int main() {
    Link* definitely_lost = new Link;

    Link* whose_next_is_indirectly_lost = new Link;
    whose_next_is_indirectly_lost->next = new Link;

    still_reachable = new Link;

    possible_lost = new Link[2] + 1;
}

它存在上面四类内存错误:

==144815== LEAK SUMMARY:
==144815==    definitely lost: 16 bytes in 2 blocks
==144815==    indirectly lost: 8 bytes in 1 blocks
==144815==      possibly lost: 16 bytes in 1 blocks
==144815==    still reachable: 8 bytes in 1 blocks

2.3. 错误详情示例

上面示例代码的错误详情如下。每个错误都会标记错误路径,用户可以很方便地查找到具体是哪一行代码引起的内存泄漏。

==144815== 8 bytes in 1 blocks are still reachable in loss record 1 of 5
==144815==    at 0x4C2B0E0: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==144815==    by 0x40068B: main (valgrind.cpp:18)
==144815== 
==144815== 8 bytes in 1 blocks are indirectly lost in loss record 2 of 5
==144815==    at 0x4C2B0E0: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==144815==    by 0x40067A: main (valgrind.cpp:16)
==144815== 
==144815== 8 bytes in 1 blocks are definitely lost in loss record 3 of 5
==144815==    at 0x4C2B0E0: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==144815==    by 0x40065E: main (valgrind.cpp:13)
==144815== 
==144815== 16 bytes in 1 blocks are possibly lost in loss record 4 of 5
==144815==    at 0x4C2B800: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==144815==    by 0x40069C: main (valgrind.cpp:20)
==144815== 
==144815== 16 (8 direct, 8 indirect) bytes in 1 blocks are definitely lost in loss record 5 of 5
==144815==    at 0x4C2B0E0: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==144815==    by 0x40066C: main (valgrind.cpp:15)

16 bytes是指泄漏的区域大小。1 blocks是指有多少个区域。同一运行路径泄漏的代码会合并到一条记录。因此有时候会出现400 bytes in 10 blocks are definitely lost的错误信息。

3. valgrind 提供的其它内存异常结果

3.1. 内存越界 invalid write of size ddd

错误信息示例如下。一般是指数组访问位置越界等。

  ==19182== Invalid write of size 4
  ==19182==    at 0x804838F: f (example.c:6)
  ==19182==    by 0x80483AB: main (example.c:11)
  ==19182==  Address 0x1BA45050 is 0 bytes after a block of size 40 alloc'd
  ==19182==    at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130)
  ==19182==    by 0x8048385: f (example.c:5)
  ==19182==    by 0x80483AB: main (example.c:11)

size 是指越界的区域大小。

3.2. 未初始化 uses of uninitialised values

未初始化信息是一颗雷,有时候不会影响程序结果,但有时候会出错。示例的错误信息如下:

Conditional jump or move depends on uninitialised value(s)
   at 0x402DFA94: _IO_vfprintf (_itoa.h:49)
   by 0x402E8476: _IO_printf (printf.c:36)
   by 0x8048472: main (tests/manuel1.c:8)

3.3. 重复释放内存 invalid free

freedelete重复删除内存,会导致程序出错。

Invalid free()
   at 0x4004FFDF: free (vg_clientmalloc.c:577)
   by 0x80484C7: main (tests/doublefree.c:10)
 Address 0x3807F7B4 is 0 bytes inside a block of size 177 free'd
   at 0x4004FFDF: free (vg_clientmalloc.c:577)
   by 0x80484C7: main (tests/doublefree.c:10)

Q. E. D.

类似文章:
一个好用的 C++性能分析工具需满足几个条件:
编程 » C++, 内存检查, Linux
获取程序占用的内存量,是一个诡异的需求。但程序写多了,有时候还真需要,尤其是代码运行出现问题的时候。
编程 » C++, C++11
花括号初始化是C++11引入的一种初始化方法。
http://senlinzhan.github.io/2017/12/04/cpp-memory-order/ 写得最浅显易懂。记录一下以备查询。
一个好用的 C++性能分析工具需满足几个条件:
编程 » C++
C++的浮点数转整数有四种方法,直接类型转换、round、floor、ceil。其效果如下表: