1.1 Valgrind
Valgrind其实是一个工具集,内存错误检测只是它众多功能的一个。Valgrind工具包包含多个工具,如Memcheck,Cachegrind,Helgrind, Callgrind,Massif。
- Memcheck。这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。这也是本文将重点介绍的部分。
- Callgrind。它主要用来检查程序中函数调用过程中出现的问题。
- Cachegrind。它主要用来检查程序中缓存使用出现的问题。
- Helgrind。它主要用来检查多线程程序中出现的竞争问题。
- Massif。它主要用来检查程序中堆栈使用中出现的问题。
下面主要介绍Memcheck的使用。
memcheck工具使用方法如下:
valgrind --tool=memcheck ./test
解释:--tool=memcheck指定了使用的工具是memcheck,./test是编译之后生成的可执行文件,使用memcheck检测test。
memcheck可以检测下列常见的与内存相关的问题:
- 未释放内存的使用
- 对释放后内存的读/写
- 对已分配内存块尾部的读/写
- 内存泄露
- 不匹配的使用malloc/new/new[] 和 free/delete/delete[]
- 重复释放内存
1.1.0 安装valgrind
valgrind下载链接:https://sourceware.org/pub/valgrind/valgrind-3.18.1.tar.bz2
valgrind安装:
1. tar -jxvf valgrind-3.18.1.tar.bz2
2. cd valgrind-3.18.1
3. ./configure
4. make
5. sudo make install
接下来使用valgrind检测代码中出现的内存问题,所有的代码需使用g++/gcc且加上-g选项。
1.1.1 使用未初始化的内存(使用野指针)
定义一个指针p,但未给它申请空间,即它是一个野指针,但我们却使用了它。
先编译gtest.cpp文件:
g++ gtest.cpp -o gtest -g
然后使用valgrand检测:
1.1.2 在内存被释放后进行读/写(使用野指针)
p所指向的内存被释放了,p变成了野指针,但是我们却继续使用这片内存。
编译并使用valgrand检测:
1.1.3 从已分配内存块的尾部进行读/写(动态内存越界)
动态的申请一个数组,然后越界访问数组:
编译并使用valgrand检测:
注意:Valgrind不检查静态分配数组的使用情况!所以对静态分配的数组,Valgrind表示无能为力!看下面的例子:
1.1.4 内存泄漏
经常遇到程序中未成对使用malloc/free和new/delete,导致内存泄漏:
我们为指针p malloc一块内存空间,在程序结束时却未free掉这块空间,检测时会提示有内存泄漏。
根据提示,我们使用--leak-check=full查看内存泄漏的详细信息,同时加上下面两个参数:
--show-reachable=yes : //是否检测控制范围之外的泄漏,比如全局指针、static指针等
--log-file=a.log //结果输出到文件
下面是这两个参数的使用:
valgrind检测的结果存放在 gtest.log中。
definitely lost:确认丢失。这就是真正的内存泄漏,最为严重,程序中存在内存泄露,必须尽快修复。当程序结束时如果一块动态分配的内存没有被释放且通过程序内的指针变量均无法访问这块内存则会报这个错误。
“indirectly lost”:间接丢失。当使用了含有指针成员的类或结构时可能会报这个错误。这类错误无需直接修复,他们总是与”definitely lost”一起出现,只要修复”definitely lost”即可。
“possibly lost”:可能丢失。大多数情况下应视为与”definitely lost”一样需要尽快修复,除非你的程序让一个指针指向一块动态分配的内存(但不是这块内存起始地址),然后通过运算得到这块内存起始地址,当程序结束时如果一块动态分配的内存没有被释放且通过程序内的指针变量均无法访问这块内存的起始地址,但可以访问其中的某一部分数据,则会报这个错误。
“still reachable”:可以访问,未丢失但也未释放。如果程序是正常结束的,那么它可能不会造成程序崩溃,但长时间运行有可能耗尽系统资源。如果程序是崩溃(如访问非法的地址而崩溃)而非正常结束的,则应当暂时忽略它,先修复导致程序崩溃的错误,然后重新检测。
“suppressed”:已被解决。出现了内存泄露但系统自动处理了。
1.1.5 不匹配地使用malloc/new/new[] 和 free/delete/delete[]
正常使用malloc/free、new/delete是这种情况:
非常使用malloc/free、new/delete是这种情况:
1.1.6 两次释放内存
调用1次申请内存,2次释放内存:
1.2 mtrace检测内存泄漏
mtrace其实是GNU扩展函数,用来跟踪malloc。
mtrace为内存分配函数(malloc, realloc, memalign, free)安装hook函数。这些hook函数记录内存的申请和释放的trace信息。这些trace信息可以被用来发现内存泄漏和释放不是申请的内存。当调用mtrace,mtrace会检查环境变量MALLOC_TRACE。该环境变量应该包含记录trace信息的文件路径。
下面是是内存泄漏的一个简单示例:
- 在程序的起始处包含头文件:#include <mcheck.h>
- 设置环境变量:export MALLOC_TRACE="mtrace.out",可以加入如下代码:
setenv("MALLOC_TRACE", "mtrace.out", 1);
函数头文件: #include<stdlib.h>
函数原型: int setenv(const char *name,const char * value,int overwrite);
函数说明: setenv()用来改变或增加环境变量的内容。
参数: name为环境变量名称字符串。
参数: value则为变量内容。
参数:overwrite为0且该环境变量已有内容,则参数value会被忽略;不为0,则改变环境变量原有内容,原有内容会被改为参数value所指的变量内容。
- 调用函数mtrace()
- 编译程序带上 -g 选项
g++ gtest.cpp -o gtest -g
- 运行程序一次,尽量调用所有程序内的函数。这时调试信息就已经被写入我们指定的mtrace.out文件中
./gtest
- 运行:mtrace ./gtest mtrace.out,查看内存监测情况
mtrace ./gtest mtrace.out
检测结果:在gtest.cpp的第10行有未释放的内存。
[root@localhost test1]# mtrace gtest mtrace.out
Memory not freed:
-----------------
Address Size Caller
0x000000000091b4b0 0x4 at /home/gaofei/vscodeProject/test1/gtest.cpp:10
[root@localhost test1]#
注: 有些centos7运行mtrace命令时显示:bash: mtrace: command not found,执行下面命令安装即可:sudo yum install glibc-utils