使⽤VisualLeakDetector检测内存泄漏[转]
1.初识VisualLeakDetector
灵活⾃由是C/C++语⾔的⼀⼤特⾊,⽽这也为C/C++程序员出了⼀个难题。当程序越来越复杂时,内存的管理也会变得越加复杂,稍有不
慎就会出现内存问题。内存泄漏是最常见的内存问题之⼀。内存泄漏如果不是很严重,在短时间内对程序不会有太⼤的影响,这也使得内存
泄漏问题有很强的隐蔽性,不容易被发现。然⽽不管内存泄漏多么轻微,当程序长时间运⾏时,其破坏⼒是惊⼈的,从性能下降到内存耗
尽,甚⾄会影响到其他程序的正常运⾏。另外内存问题的⼀个共同特点是,内存问题本⾝并不会有很明显的现象,当有异常现象出现时已时
过境迁,其现场已⾮出现问题时的现场了,这给调试内存问题带来了很⼤的难度。
1、可以得到内存泄漏点的调⽤堆栈,如果可以的话,还可以得到其所在⽂件及⾏号;
2、可以得到泄露内存的完整数据;
3、可以设置内存泄露报告的级别;
4、它是⼀个已经打包的lib,使⽤时⽆须编译它的源代码。⽽对于使⽤者⾃⼰的代码,也只需要做很⼩的改动;
5、他的源代码使⽤GNU许可发布,并有详尽的⽂档及注释。对于想深⼊了解堆内存管理的读者,是⼀个不错的选择。
可见,从使⽤⾓度来讲,VisualLeakDetector简单易⽤,对于使⽤者⾃⼰的代码,唯⼀的修改是#includeVisualLeakDetector的头⽂件
后正常运⾏⾃⼰的程序,就可以发现内存问题。从研究的⾓度来讲,如果深⼊VisualLeakDetector源代码,可以学习到堆内存分配与释放的
原理、内存泄漏检测的原理及内存操作的常⽤技巧等。
本⽂⾸先将介绍VisualLeakDetector的使⽤⽅法与步骤,然后再和读者⼀起初步的研究VisualLeakDetector的源代码,去了解Visual
LeakDetector的⼯作原理。
2.使⽤VisualLeakDetector(1.0)
下⾯让我们来介绍如何使⽤这个⼩巧的⼯具。
⾸先从⽹站上下载zip包,解压之后得到vld.h,vldapi.h,,,,等⽂件。将.h⽂件拷贝到VisualC++的默
认include⽬录下,将.lib⽂件拷贝到VisualC++的默认lib⽬录下,便安装完成了。因为版本问题,如果使⽤windows2000或者以前的版本,
需要将拷贝到你的程序的运⾏⽬录下,或其他可以引⽤到的⽬录。
接下来需要将其加⼊到⾃⼰的代码中。⽅法很简单,只要在包含⼊⼝函数的.cpp⽂件中包含vld.h就可以。如果这个cpp⽂件包含
了stdafx.h,则将包含vld.h的语句放在stdafx.h的包含语句之后,否则放在最前⾯。如下是⼀个⽰例程序:
#include
voidmain()
{
…
}
接下来让我们来演⽰如何使⽤VisualLeakDetector检测内存泄漏。下⾯是⼀个简单的程序,⽤new分配了⼀个int⼤⼩的堆内存,并没有
释放。其申请的内存地址⽤printf输出到屏幕上。
#include
#include
#include
voidf()
{
int*p=newint(0x12345678);
printf("p=%08x,",p);
}
voidmain()
{
f();
}
编译运⾏后,在标准输出窗⼝得到:
p=003a89c0
在VisualC++的Output窗⼝得到:
WARNING:VisualLeakDetectordetectedmemoryleaks!
----------Block57at0x003A89C0:4bytes------------57号块0x003A89C0地址泄漏了4个字节
CallStack:--下⾯是调⽤堆栈
d:(7):f--表⽰在第7⾏的f()函数
d:(14):main–双击以引导⾄对应代码处
f:rtmvctoolscrt_bldlf_x86crtsrccrtexe.c(586):__tmainCRTStartup
f:rtmvctoolscrt_bldlf_x86crtsrccrtexe.c(403):mainCRTStartup
0x7C816D4F(Fileandlinenumbernotavailable):RegisterWaitForInputIdle
Data:--这是泄漏内存的内容,0x12345678
78563412xV4.............
VisualLeakDetectordetected1memoryleak.
第⼆⾏表⽰57号块有4字节的内存泄漏,地址为0x003A89C0,根据程序控制台的输出,可以知道,该地址为指针p。程序的第7⾏,f()函数
⾥,在该地址处分配了4字节的堆内存空间,并赋值为0x12345678,这样在报告中,我们看到了这4字节同样的内容。
可以看出,对于每⼀个内存泄漏,这个报告列出了它的泄漏点、长度、分配该内存时的调⽤堆栈、和泄露内存的内容(分别以16进制和⽂本
格式列出)。双击该堆栈报告的某⼀⾏,会⾃动在代码编辑器中跳到其所指⽂件的对应⾏。这些信息对于我们查找内存泄露将有很⼤的帮
助。
这是⼀个很⽅便易⽤的⼯具,安装后每次使⽤时,仅仅需要将它头⽂件包含进来重新build就可以。⽽且,该⼯具仅在buildDebug版的时候
会连接到你的程序中,如果buildRelea版,该⼯具不会对你的程序产⽣任何性能等⽅⾯影响。所以尽可以将其头⽂件⼀直包含在你的源代
码中。
LeakDetector⼯作原理
下⾯让我们来看⼀下该⼯具的⼯作原理。
在这之前,我们先来看⼀下VisualC++内置的内存泄漏检测⼯具是如何⼯作的。VisualC++内置的⼯具CRTDebugHeap⼯作原来很简
单。在使⽤Debug版的malloc分配内存时,malloc会在内存块的头中记录分配该内存的⽂件名及⾏号。当程序退出时CRT会在main()函数返
回之后做⼀些清理⼯作,这个时候来检查调试堆内存,如果仍然有内存没有被释放,则⼀定是存在内存泄漏。从这些没有被释放的内存块的
头中,就可以获得⽂件名及⾏号。
这种静态的⽅法可以检测出内存泄漏及其泄漏点的⽂件名和⾏号,但是并不知道泄漏究竟是如何发⽣的,并不知道该内存分配语句是如
何被执⾏到的。要想了解这些,就必须要对程序的内存分配过程进⾏动态跟踪。VisualLeakDetector就是这样做的。它在每次内存分配时将
其上下⽂记录下来,当程序退出时,对于检测到的内存泄漏,查找其记录下来的上下⽂信息,并将其转换成报告输出。
3.1初始化
VisualLeakDetector要记录每⼀次的内存分配,⽽它是如何监视内存分配的呢?Windows提供了分配钩⼦(allocationhooks)来监视调试
堆内存的分配。它是⼀个⽤户定义的回调函数,在每次从调试堆分配内存之前被调⽤。在初始化时,VisualLeakDetector使
⽤_CrtSetAllocHook注册这个钩⼦函数,这样就可以监视从此之后所有的堆内存分配了。
如何保证在VisualLeakDetector初始化之前没有堆内存分配呢?全局变量是在程序启动时就初始化的,如果将VisualLeakDetector作为
⼀个全局变量,就可以随程序⼀起启动。但是C/C++并没有约定全局变量之间的初始化顺序,如果其它全局变量的构造函数中有堆内存分
配,则可能⽆法检测到。VisualLeakDetector使⽤了C/C++提供的#pragmainit_g来在某种程度上减少其它全局变量在其之前初始化的概
率。根据#pragmainit_g的定义,全局变量的初始化分三个阶段:⾸先是compiler段,⼀般c语⾔的运⾏时库在这个时候初始化;然后
是lib段,⼀般⽤于第三⽅的类库的初始化等;最后是ur段,⼤部分的初始化都在这个阶段进⾏。VisualLeakDetector将其初始化设置
在compiler段,从⽽使得它在绝⼤多数全局变量和⼏乎所有的⽤户定义的全局变量之前初始化。
4.记录内存分配
⼀个分配钩⼦函数需要具有如下的形式:
intYourAllocHook(intallocType,void*urData,size_tsize,intblockType,longrequestNumber,constunsignedchar*filename,intlineNumber);
就像前⾯说的,它在VisualLeakDetector初始化时被注册,每次从调试堆分配内存之前被调⽤。这个函数需要处理的事情是记录下此时
的调⽤堆栈和此次堆内存分配的唯⼀标识——requestNumber。
得到当前的堆栈的⼆进制表⽰并不是⼀件很复杂的事情,但是因为不同体系结构、不同编译器、不同的函数调⽤约定所产⽣的堆栈内容
略有不同,要解释堆栈并得到整个函数调⽤过程略显复杂。不过windows提供⼀个StackWalk64函数,可以获得堆栈的内
容。StackWalk64的声明如下:
BOOLStackWalk64(
DWORDMachineType,
HANDLEhProcess,
HANDLEhThread,
LPSTACKFRAME64StackFrame,
PVOIDContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE64ReadMemoryRoutine,
PFUNCTION_TABLE_ACCESS_ROUTINE64FunctionTableAccessRoutine,
PGET_MODULE_BASE_ROUTINE64GetModuleBaRoutine,
PTRANSLATE_ADDRESS_ROUTINE64TranslateAddress
);
STACKFRAME64结构表⽰了堆栈中的⼀个frame。给出初始的STACKFRAME64,反复调⽤该函数,便可以得到内存分配点的调⽤堆栈
了。
//Walkthestack.
while(count<_VLD_maxtraceframes){
count++;
if(!pStackWalk64(architecture,m_process,m_thread,&frame,&context,
NULL,pSymFunctionTableAccess64,pSymGetModuleBa64,NULL)){
//Couldn'ttracebackthroughanymoreframes.
break;
}
本文发布于:2022-11-24 15:43:55,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/12737.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |