C语言中一个malloc调用与一个free调用对应出现。
当项目足够大的时候,我们就很容易忘记free内存,或者重复free内存。
在一般Linux发行版中,有一些自带的内存检查工具可以帮助我们去检查对手动分配的内存的free情况,其中的一个工具就是mtrace。
使用mtrace之前需要在C源码中使用GNU C函数mtrace追踪记录内存的分配释放情况。
mtrace()包含在头文件mcheck.h中,与之配对的函数是muntrace(),函数原型分别为:
#include <mcheck.h>
void mtrace(void);
void muntrace(void);
调用mtrace追踪前需要设置环境变量MALLOC_TRACE,可以使用export命令也可以使用setenv函数:
export: export MALLOC_TRACE="<LogName>"
或者:
setenv: assert(!setevn("MALLOC_TRACE", "<LogName>", 1));
其中<LogName>是log文件名,用来保存mtrace生成的追踪记录。
事例中将使用export命令方法。
使用mtrace追踪内存申请与释放的流程:([]表示可选项,可有可无)
1:export MALLOC_TRACE="<LogName>"
2:在C源码中加入mtrace();
3:gcc -g <SourceFile> [-o DestinationBin]
4:执行程序
5:mtrace [DestinationBin] <LogName>
事例:
1:设置环境变量
export MALLOC_TRACE=mcheck.log
export可以指定log的路径,也可以不指定,则生成的log文件在保存在程序执行的目录下。
2:源码
//FileName: mcheck.c
#include <mcheck.h>
#include <malloc.h>
int main()
{
mtrace(); //调用mtrace()函数打开追踪
char *M1 = malloc(10); //申请10(0xa)字节内存
char *M2 = malloc(15); //申请15(0xf)字节内存
//free(M1); //不释放M1内存
free(M2); //释放M2内存
return 0;
}
3:编译
gcc -g -o mcheck mcheck.c
4:执行程序
5:mtrace mcheck mcheck.log
执行完程序后,会在程序执行的当前目录下生成一个名为mcheck.log的文件,文件内容:
= Start
@ ./mcheck:[0x804844a] + 0x9780378 0xa
@ ./mcheck:[0x8048456] + 0x9780388 0xf
@ ./mcheck:[0x804845e] - 0x9780388
里面保存了申请内存的超始地址和内存大小,比如0x9780378是M1指向的起始地址,0xa(10)内存大小,而前面的"+"号表示申请内存,"-"表示释放内存。
从上面可以看出程序释放了起始地址为0x9780388的空间,但0x9780378开始的内存空间没有进行释放的记录。从0xa和0xf可以看出0x9780378是M1所指向的空间而0x9780388是M2所指向的空间,从代码可看出程序对M2的空间进行的释放而没有对M1的内存空间进行释放。因此检查结果符合实际情况。
上面的例程很短,可以直接从log文件中直接看出问题所在。但是如果程序足够大,我们就很难去查找问题,因此我们可以使用工具mtrace去分析log文件。
执行第5步时会在终端下输出:
Memory not freed:
-----------------
Address Size Caller
0x09780378 0xa at /home/fyl/TestPro/mcheck.c:9
从输出的第1行就可以发现了例程没有free内存,而没有释放的内存空间起始地址是:0x09780378,内存空间大小:0xa,调用的地方:mcheck.c的第9行。
于是我们就可以根据实际需求去释放内容,从而避免在程序运行时出现内存泄漏。
如果我们执行第5步时没有加上相应的程序名,而是这样执行:mtrace mtrace.log,就会输出以下信息:
Memory not freed:
-----------------
Address Size Caller
0x09780378 0xa at 0x804844a
比较一下,就会发现caller列变成了一个地址,而不是源文件所在的位置。
为了能你上面的caller那样可以显示出申请内存的操作在源文件中的位置,在编译程序时需要添加gcc选项-g。
前面说到与mtrace调用相匹配的调用是muntrace,至于使用时候调用起muntrace看自身情况需求。但是一般情况下可以在源文件最后添加,也可以不显式调用它,而是由程序自行结束时关闭mtrace。
因为申请过的内存,有可能不是马上释放的,如果调用了muntrace就有可能检查不正确。
将源代码修改一下:
//FileName: mcheck.c
#include <mcheck.h>
#include <malloc.h>
int main()
{
mtrace(); //调用mtrace()函数打开追踪
char *M1 = malloc(10); //申请10(0xa)字节内存
char *M2 = malloc(15); //申请15(0xf)字节内存
free(M1); //不释放M1内存
free(M2); //释放M2内存
//M2 = NULL; //不置NULL
free(M2); //第2次释放M2内存
return 0;
}
如果不添加mtrace()调用,则上面例程会报出:
*** glibc detected *** ./mcheck: double free or corruption (fasttop): 0x099de018 ***
======= Backtrace: =========
/lib/libc.so.6[0xc85c81]
/lib/libc.so.6[0xc885c2]
./mcheck[0x8048445]
/lib/libc.so.6(__libc_start_main+0xe6)[0xc2bd36]
./mcheck[0x8048361]
======= Memory map: ========
0027a000-0027b000 r-xp 00000000 00:00 0 [vdso]
00b54000-00b71000 r-xp 00000000 fd:00 2760424 /lib/libgcc_s-4.4.7-20120601.so.1
00b71000-00b72000 rw-p 0001d000 fd:00 2760424 /lib/libgcc_s-4.4.7-20120601.so.1
00bf3000-00c11000 r-xp 00000000 fd:00 2760408 /lib/ld-2.12.so
00c11000-00c12000 r--p 0001d000 fd:00 2760408 /lib/ld-2.12.so
00c12000-00c13000 rw-p 0001e000 fd:00 2760408 /lib/ld-2.12.so
00c15000-00da5000 r-xp 00000000 fd:00 2760409 /lib/libc-2.12.so
00da5000-00da6000 ---p 00190000 fd:00 2760409 /lib/libc-2.12.so
00da6000-00da8000 r--p 00190000 fd:00 2760409 /lib/libc-2.12.so
00da8000-00da9000 rw-p 00192000 fd:00 2760409 /lib/libc-2.12.so
00da9000-00dac000 rw-p 00000000 00:00 0
08048000-08049000 r-xp 00000000 fd:02 524499 /home/fyl/TestPro/mcheck
08049000-0804a000 rw-p 00000000 fd:02 524499 /home/fyl/TestPro/mcheck
099de000-099ff000 rw-p 00000000 00:00 0 [heap]
b7600000-b7621000 rw-p 00000000 00:00 0
b7621000-b7700000 ---p 00000000 00:00 0
b7731000-b7732000 rw-p 00000000 00:00 0
b7743000-b7745000 rw-p 00000000 00:00 0
bfb78000-bfb8d000 rw-p 00000000 00:00 0 [stack]
已放弃 (core dumped)
如果添加了mtrace(),程序只报
*** glibc detected *** ./mcheck: double free or corruption (fasttop): 0x09dbc388 ***
之后就卡住了,至少在机子上测试时是这样。
因此在释放内存后需要将指针置NULL,因为free(NULL)是没问题的。
但是如果人人都注意到些问题,那么也就既不会忘记free,也不会多次free了,更不需要测试了。
因此,mtrace没办法检查下去,就需要使用其他方法了。