使用C或者C++编程的时候,最担心的应该还是内存泄漏问题,测试人员通常专注于功能/性能测试,如果有不明显的内存泄漏,可能要在程序发布后几天,甚至更久才会发现故障。因此,检查C/C++程序的内存泄漏,开发人员需要自己把好这个关。
检测内存泄漏的工具有很多,大致分为二种,一种是嵌入程序的,需要修改源码;一种是attach到进程,实时监测的。
LeakDiag是微软的一款内存泄漏检测工具,免费的,不需要和待检测的源程序一起编译,在使用上较为方便,能够统计一段时间内的内存泄漏大小以及指出发生内存泄漏的源代码的行数。
首先先去微软网站下载一个LeakDiag ,安装时候需要注意:务必按照默认路径安装,否则可能出问题。
试试用法,
首先准备一个有bug的程序,如下,工程的名称就叫LEAK,代码敲完,VC下编译运行。
#include "stdio.h"
#include "malloc.h"
#include "windows.h"
void
main(
void
)
{
int
i;
char
*p;
for
(i=0; i<1000; ++i)
{
p = (
char
*)
malloc
(
sizeof
(
char
)*5);
Sleep(2000);
printf
(
"i=[%d]\n"
,i);
}
}
|
使用malloc来分配内存,但没有free掉,运行以后,打开LeakDiag,如下:
选择进程列表中的LEAK.EXE,由于malloc分配的内存在堆上,我们点击Windows Heap Allocator,再点Start按钮,就开始跟踪堆上的内存分配了。
运行一会,按Log按钮,就会在安装根目录下的Logs文件夹下生成Log文件,XML结构的,用IE,打开后,我们关注的是
<
LEAKS
ver
=
"1.25.28.2201"
>
<
STACK
numallocs
=
"015"
size
=
"048"
totalsize
=
"0720"
>
<
STACKSTATS
>
<
SIZESTAT
size
=
"048"
numallocs
=
"015"
/>
<
HEAPSTAT
handle
=
"570000"
numallocs
=
"015"
/>
</
STACKSTATS
>
<
FRAME
num
=
"0"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x403492"
offset
=
"0x00003492"
/>
<
FRAME
num
=
"1"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x401352"
offset
=
"0x00001352"
/>
<
FRAME
num
=
"2"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x401159"
offset
=
"0x00001159"
/>
<
FRAME
num
=
"3"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x4010d9"
offset
=
"0x000010D9"
/>
<
FRAME
num
=
"4"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x40104a"
offset
=
"0x0000104A"
/>
<
STACKID
>003B4008</
STACKID
>
</
STACK
>
</
LEAKS
>
|
我们改一下代码再试试。然后再详解生成的log文档的含义。
#include "stdio.h"
#include "malloc.h"
#include "windows.h"
void
main(
void
)
{
int
i;
char
*p;
for
(i=0; i<1000; ++i)
{
p = (
char
*)
malloc
(
sizeof
(
char
)*5);
Sleep(2000);
printf
(
"i=[%d]\n"
,i);
free
(p);
}
}
|
现在每一个malloc匹配一个free了。
再次运行LEAK.exe,然后运行LeakDiag在诊断,点Start,同样是跟踪堆内存的分配,过一会后按Log按钮,再按Stop停止跟踪。
还是有文件生成了,怎么还有泄漏?
打开后内容如下:
<
LEAKS
ver
=
"1.25.28.2201"
>
<
STACK
numallocs
=
"01"
size
=
"048"
totalsize
=
"048"
>
<
STACKSTATS
>
<
SIZESTAT
size
=
"048"
numallocs
=
"01"
/>
<
HEAPSTAT
handle
=
"620000"
numallocs
=
"01"
/>
</
STACKSTATS
>
<
FRAME
num
=
"0"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x403492"
offset
=
"0x00003492"
/>
<
FRAME
num
=
"1"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x401352"
offset
=
"0x00001352"
/>
<
FRAME
num
=
"2"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x401159"
offset
=
"0x00001159"
/>
<
FRAME
num
=
"3"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x4010d9"
offset
=
"0x000010D9"
/>
<
FRAME
num
=
"4"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x40104a"
offset
=
"0x0000104A"
/>
<
STACKID
>005E4008</
STACKID
>
</
STACK
>
</
LEAKS
>
|
来看这一段:
numallocs表示内存泄漏的次数,size是泄漏的内存大小,totalsize = size * numallocs。
而刚才上面那个是numallocs=”15″。如此,修改后的程序之所以出现log日志,应该是按下Log按钮的时候,程序还没跑到free(p)这一行。所以LeakDiag认为是一个内存泄漏。
再修改一下程序看看,这次把malloc和free都去掉了。
#include "stdio.h"
#include "windows.h"
void
main(
void
)
{
int
i;
char
*p;
for
(i=0; i<1000; ++i)
{
Sleep(2000);
printf
(
"i=[%d]\n"
,i);
free
(p);
}
}
|
这次程序运行后,再用LeakDiag来跟踪,没有出现Log日志,确实没有内存泄漏呀。
来分析之前的问题程序产生的XML文件:
<
STACK
numallocs
=
"01"
size
=
"048"
totalsize
=
"048"
>
|
这一行已经解释过了。
<
FRAME
num
=
"0"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x403492"
offset
=
"0x00003492"
/>
<
FRAME
num
=
"1"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x401352"
offset
=
"0x00001352"
/>
<
FRAME
num
=
"2"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x401159"
offset
=
"0x00001159"
/>
<
FRAME
num
=
"3"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x4010d9"
offset
=
"0x000010D9"
/>
<
FRAME
num
=
"4"
dll
=
"LEAK.exe"
function
=
""
filename
=
""
line
=
""
addr
=
"0x40104a"
offset
=
"0x0000104A"
/>
|
是栈回溯信息,这是内存泄漏时候的call stack,可以通过配置Option菜单里的内容来修改栈回溯的深度。
当然,现在从这份信息里看不出程序问题在哪里,我们知道内存泄漏了,究竟在哪一行呢。
现在打开LeakDiag的图形界面,按菜单上的Options,配置为以下内容再重新做一次针对第一个问题代码的跟踪。
Log file location, 这个不解释
Symbol search path,配置为编译后生成的pdb文件所在的目录,LeakDiag需要借助正确的符号来产生有用的栈回溯信息,只有正确地指定符号文件的路径,才可以得到有用信息,否则在log信息的节点内的栈回溯信息中将只能看到每个栈帧(Frame)的地址。
这次的结果如下:
<
LEAKS
ver
=
"1.25.28.2201"
>
<
STACK
numallocs
=
"03"
size
=
"048"
totalsize
=
"0144"
>
|