使用 Valgrind 检测内存泄漏
介绍
Valgrind
是一个开源的内存调试、内存泄漏检测和性能分析工具,主要用于 C/C++ 程序 的调试。它在程序运行时对其进行“动态二进制插桩”,可以捕捉各种内存问题。Valgrind
常用于查找:
- 内存泄漏(memory leaks)
- 未初始化内存的使用(use of uninitialized memory)
- 越界访问(buffer overflows/underflows)
- 重复释放(double free)
- 未释放内存
- 非法访问已释放内存
Valgring
包含以下常用工具组件
工具名 | 功能 |
---|---|
Memcheck | 默认工具,检测内存错误 |
Callgrind | 性能分析,检查函数调用过程中出现的问题 |
Cachegring | 分析CPU的cache命中率、丢失率,用于代码优化 |
Massif | 堆内存使用分析(heap profiler) |
Helgrind | 多线程数据竞争检测 |
DRD | 多线程程序中的同步错误检测 |
安装
- 编译安装
1
2
3
4
5
6
7
8
9
10
11
wget https://sourceware.org/pub/valgrind/valgrind-3.25.1.tar.bz2
# 最新版链接参考:https://valgrind.org/downloads/current.html
tar -jxvf valgrind-3.25.1.tar.bz2
# -j表示解压格式为bz2
cd valgrind-3.25.1.tar.bz2
./configure
make && make install
安装完成后可以用valgrind --version
检查是否安装成功
使用
memcheck
最常用的工具,用来检测程序中出现的内存问题,所有对内存的读写都会被检测到,一切对malloc
、free
、new
、delete
的调用都会被捕获。
使用方法
1
2
3
4
valgrind ./program
# 完整写法,memcheck为默认工具,可以忽略
# valgrind --tool=memcheck
参数选项
选项 | 作用 |
---|---|
–tool=memcheck | 指定工具,默认为memcheck |
–leak-check=full | 详细输出内存泄漏信息 |
–show-leak-kinds=all | 显示每一处泄漏的栈信息 |
–undef-value-errors=no | 关闭“未定义值使用”警告 |
–log-file=log | 将输出保存到文件 |
在log文件最后会有个summary,其中对内存泄露进行了分类,总共有五类:
(1)
definitely lost
意味着你的程序一定存在内存泄露;(2)
indirectly lost
意味着你的程序一定存在内存泄露,并且泄露情况和指针结构相关(3)
possibly lost
意味着你的程序一定存在内存泄露,除非你是故意进行着不符合常规的操作,例如将指针指向某个已分配内存块的中间位置。(4)
still reachable
意味着你的程序可能是没问题的,但确实没有释放掉一些本可以释放的内存。这种情况是很常见的,并且通常基于合理的理由。(5)
suppressed
意味着有些泄露信息被压制了。在默认的 suppression 文件中可以看到一些 suppression 相关设置。
Callgrind
Callgrind
收集程序运行时的一些数据,函数调用关系等信息,还可以有选择地进行cache
模拟。在运行结束时,它会把分析数据写入一个文件。
callgrind_annotate
可以把这个文件的内容转化成可读的形式。
Cachegrind
它模拟 CPU中的一级缓存I1,D1和L2二级缓存,能够精确地指出程序中 cache
的丢失和命中。如果需要,它还能够为我们提供cache
丢失次数,内存引用次数,以及每行代码,每个函数,每个模块,整个程序产生的指令数。这对优化程序有很大的帮助。
Helgrind
它主要用来检查多线程程序中出现的竞争问题。Helgrind
寻找内存中被多个线程访问,而又没有一贯加锁的区域,这些区域往往是线程之间失去同步的地方,而且会导致难以发掘的错误。Helgrind
实现了名为Erase
的竞争检测算法,并做了进一步改进,减少了报告错误的次数。
Massif
堆栈分析器,它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小。
Massif能帮助我们减少内存的使用,在带有虚拟内存的现代系统中,它还能够加速我们程序的运行,减少程序停留在交换区中的几率。
使用案例
C变量未定义和缺少参数
程序例子如下:
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main() {
int age = 10;
int height;
printf("I am %d years old.\n");
printf("I am %d inches tall.\n", height);
return 0;
}
编译并检查
1
2
make example
valgrind ./example
有以下的输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
==554832== Memcheck, a memory error detector
==554832== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==554832== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==554832== Command: ./ex3
==554832==
I am -16776008 years old.
==554832== Conditional jump or move depends on uninitialised value(s)
==554832== at 0x48DAAD6: __vfprintf_internal (vfprintf-internal.c:1516)
==554832== by 0x48C479E: printf (printf.c:33)
==554832== by 0x109188: main (ex3.c:8)
==554832==
==554832== Use of uninitialised value of size 8
==554832== at 0x48BE2EB: _itoa_word (_itoa.c:177)
==554832== by 0x48D9ABD: __vfprintf_internal (vfprintf-internal.c:1516)
==554832== by 0x48C479E: printf (printf.c:33)
==554832== by 0x109188: main (ex3.c:8)
==554832==
==554832== Conditional jump or move depends on uninitialised value(s)
==554832== at 0x48BE2FC: _itoa_word (_itoa.c:177)
==554832== by 0x48D9ABD: __vfprintf_internal (vfprintf-internal.c:1516)
==554832== by 0x48C479E: printf (printf.c:33)
==554832== by 0x109188: main (ex3.c:8)
==554832==
==554832== Conditional jump or move depends on uninitialised value(s)
==554832== at 0x48DA5C3: __vfprintf_internal (vfprintf-internal.c:1516)
==554832== by 0x48C479E: printf (printf.c:33)
==554832== by 0x109188: main (ex3.c:8)
==554832==
==554832== Conditional jump or move depends on uninitialised value(s)
==554832== at 0x48D9C05: __vfprintf_internal (vfprintf-internal.c:1516)
==554832== by 0x48C479E: printf (printf.c:33)
==554832== by 0x109188: main (ex3.c:8)
==554832==
I am 0 inches tall.
==554832==
==554832== HEAP SUMMARY:
==554832== in use at exit: 0 bytes in 0 blocks
==554832== total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==554832==
==554832== All heap blocks were freed -- no leaks are possible
==554832==
==554832== Use --track-origins=yes to see where uninitialised values come from
==554832== For lists of detected and suppressed errors, rerun with: -s
==554832== ERROR SUMMARY: 5 errors from 5 contexts (suppressed: 0 from 0)
Use of uninitialised value of size 8
的意思是大小为8的未初始化的值,表示程序直接使用了未初始化的值进行计算或操作
Conditional jump or move depends on uninitialised value(s)
,这个错误表示程序的控制流(如if语句、循环、函数调用的参数传递等)依赖于未初始化的值。
C检测内存泄漏
编写一个存在未释放内存的例子:
1
2
3
4
5
6
#include <stdlib.h>
int main()
{
int *array = malloc(sizeof(int));
return 0;
}
编译程序,注意编译时要加上-g
选项
1
gcc -g -o error error.c
接着使用valgrind
去检查程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ valgrind --leak-check=full ./errro
==517874== Memcheck, a memory error detector
==517874== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==517874== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==517874== Command: ./errro
==517874==
==517874==
==517874== HEAP SUMMARY:
==517874== in use at exit: 4 bytes in 1 blocks
==517874== total heap usage: 1 allocs, 0 frees, 4 bytes allocated
==517874==
==517874== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==517874== at 0x484880F: malloc (vg_replace_malloc.c:446)
==517874== by 0x10915E: main (error.c:4)
==517874==
==517874== LEAK SUMMARY:
==517874== definitely lost: 4 bytes in 1 blocks
==517874== indirectly lost: 0 bytes in 0 blocks
==517874== possibly lost: 0 bytes in 0 blocks
==517874== still reachable: 0 bytes in 0 blocks
==517874== suppressed: 0 bytes in 0 blocks
==517874==
==517874== For lists of detected and suppressed errors, rerun with: -s
==517874== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
先看看输出信息中的HEAP SUMMARY
,它表示程序在堆上分配内存的情况,其中的1 allocs
表示程序分配了 1 次内存,0 frees
表示程序释放了 0 次内存,4 bytes allocated
表示分配了 4 个字节的内存。
另外,Valgrind 也会报告程序是在哪个位置发生内存泄漏。by 0x10915E: main (error.c:4)
表示错误发生在error.c
的第四行
LEAK SUMMARY
则会显示内存泄漏的总体情况
检测越界访问
编写以下程序:
1
2
3
4
5
6
7
8
#include <vector>
#include <iostream>
int main()
{
std::vector<int> v(10, 0);
std::cout << v[10] << std::endl;
return 0;
}
编译并执行分析:
1
2
g++ -g -o main main.cpp
valgrind --tool=memcheck --leak-check=full ./main
会有以下的分析结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
==533948== Memcheck, a memory error detector
==533948== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==533948== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==533948== Command: ./main
==533948==
==533948== Invalid read of size 4
==533948== at 0x1092EE: main (main.cpp:6)
==533948== Address 0x4dd6ca8 is 0 bytes after a block of size 40 alloc'd
==533948== at 0x4848F95: operator new(unsigned long) (vg_replace_malloc.c:487)
==533948== by 0x109B67: __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (new_allocator.h:127)
==533948== by 0x109A85: std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (alloc_traits.h:464)
==533948== by 0x1099B7: std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (stl_vector.h:346)
==533948== by 0x10982A: std::_Vector_base<int, std::allocator<int> >::_M_create_storage(unsigned long) (stl_vector.h:361)
==533948== by 0x109636: std::_Vector_base<int, std::allocator<int> >::_Vector_base(unsigned long, std::allocator<int> const&) (stl_vector.h:305)
==533948== by 0x109488: std::vector<int, std::allocator<int> >::vector(unsigned long, int const&, std::allocator<int> const&) (stl_vector.h:524)
==533948== by 0x1092D0: main (main.cpp:5)
==533948==
0
==533948==
==533948== HEAP SUMMARY:
==533948== in use at exit: 0 bytes in 0 blocks
==533948== total heap usage: 3 allocs, 3 frees, 73,768 bytes allocated
==533948==
==533948== All heap blocks were freed -- no leaks are possible
==533948==
==533948== For lists of detected and suppressed errors, rerun with: -s
==533948== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Invalid read of size 4
表示越界读取 4 个字节,这个操作出现在main.cpp
文件的第 6 行。
另外可以看到,vector
分配了一块 40 字节的内存,程序越界访问紧急着这块内存之后的 4 个字节。
检测未初始化的内存
程序如下:
1
2
3
4
5
6
7
8
9
10
#include <iostream>
int main()
{
int x;
if (x == 0)
{
std::cout << "X is zero" << std::endl;
}
return 0;
}
编译后进行分析,结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
==534112== Memcheck, a memory error detector
==534112== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==534112== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==534112== Command: ./main
==534112==
==534112== Conditional jump or move depends on uninitialised value(s)
==534112== at 0x1091B9: main (main.cpp:5)
==534112==
X is zero
==534112==
==534112== HEAP SUMMARY:
==534112== in use at exit: 0 bytes in 0 blocks
==534112== total heap usage: 2 allocs, 2 frees, 73,728 bytes allocated
==534112==
==534112== All heap blocks were freed -- no leaks are possible
==534112==
==534112== Use --track-origins=yes to see where uninitialised values come from
==534112== For lists of detected and suppressed errors, rerun with: -s
==534112== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
输出中提示了main.cpp
文件的第 5 行访问了未初始化的内存
如果使用--undef-value-errors=no
选项,会跳过这个错误