近几日分析了windows版本的一个内存泄露故障,之前之后补了下功课,记录如下,抽时间分了近10次才补充完整,可能不太连贯,只是对所用知识的一个提示,欲知详情还是msdn等来的实在。
本文用到的几个很重要的对内存分配相关的结构定义:
所分配内存两侧的NoMansLand用0xFD填充,上图注释部分data的地址为对分配函数返回的地址。前后用0xFD填充
对以释放的则用0xDD填充,Dead Data
新申请的则用0xCD填充,ClearData
_CrtCheckMemory 在应用程序代码中调用,用于检查堆得完整性。检查堆得每个内存块,验证内存块头有效,并确认尚未修改缓冲区。
c运行时库包含new delete的Debug版本,当定义_CRTDBG_MAP_ALLOC及_DEBUG时自动替换为其debug版本,将记录分配内存时的文件名及行数。如果想要使用_CLIENT_BLOCK类型的内存分配,则不能定义_CRTDBG_MAP_ALLOC而是需要直接使用new的debug版本,或创建替换new运算符的宏。
若要捕获某时刻堆内存的快照,可以使用上述开始介绍的_CrtMemState 结构。该结构保存指向调试堆最近分配的内存块的指针。具体参见前面结构中的字段描述。
下列函数报告堆的状态和内容,并使用这些信息帮助检测内存泄漏及其他问题:
【1】_CrtMemCheckpoint:
在_CrtMemState 结构中保存堆的快照,_CrtmemState类型变量有程序定义,此处接受的是该变量地址。
如_CrtMemState tMS1;
_CrtMemCheckpoint(&tMS1);
【2】_CrtMemDifference:
比较两个内存状态结构oldState与newState,在第三个状态结构stateDiff中保存二者之间的差异,如果两个状态不同,则返回 TRUE。
【3】
_CrtMemDumpStatistics:
转储给定的 _CrtMemState 结构。该结构可能包含给定_CrtMemState结构即堆状态的快照或两个快照之间的差异。
【4】
_CrtMemDumpAllObjectsSince:
转储相对于给定的内存状态以来或从执行开始以来所分配的所有对象的信息。如果已经使用 _CrtSetDumpClient 安装了挂钩函数,那么,_CrtMemDumpAllObjectsSince 每次转储 _CLIENT_BLOCK 块时,都会调用应用程序所提供的挂钩函数。
下面的_CrtDumpMemoryLeaks的函数实现贴在下面了,该函数对于检查程序生命周期的内存泄露很有用处,看起实现无非是从NULL开始到现在的内存快照还有未释放的内存块时,调用下本函数,其中给定的内存状态为NULL,表示从程序最开始以来,因而_CrtDumpMemoryLeaks也应该在函数最后结束的出口处调用才对,否则容易误报。
对于我们目前的应用系统启动过程中初始化配置等会申请大量内存,之后正常情况应该是稳定运行,不该结束,因而想要检测内存泄露,可以在确认程序启动后坐下内存快照,然后运行之中定时或定量(即处理了多少次接入请求之后)去做检测。
检测报告应该相对于最开始的内存快照进行,理论上如果存在内存泄露应该在多次输出的文件中都应该看到该次内存块的分配才对。如果只是在某一两次输出能看到该内存块为释放,而随后的输出中就没了则说明只是迟了一点,也不算内存泄露。
【5】
_CrtDumpMemoryLeaks :
确定自程序开始执行以来是否发生过内存泄漏,如果发生过,则转储所有已分配对象。如果已使用 _CrtSetDumpClient 安装了挂钩函数,那么,_CrtDumpMemoryLeaks 每次转储 _CLIENT_BLOCK 块时,都会调用应用程序所提供的挂钩函数。
最后给出一个非常短小的vc代码的例子
输出:
Dumping objects ->
e:/develop/test/console/console.cpp(19) : {50} normal block at 0x002C0F70, 20 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
e:/develop/test/console/console.cpp(18) : {49} normal block at 0x002C0F28, 10 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD
Object dump complete.
按我的试验,如果定义了new malloc的宏,则_CRTDBG_MAP_ALLOC其实定义不定义效果一样了。
反之如果定义了_CRTDBG_MAP_ALLOC则malloc就不用如上定义了,因为会由crtdbg.h中的
#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
体现。
但是因为new被定义为了内联
inline void* __cdecl operator new(unsigned int s)
{ return ::operator new(s, _NORMAL_BLOCK, __FILE__, __LINE__); }
因而还需要定义new为宏,否则打印出来的用new申请内存的地址文件名就是crtdbg.h的上面这一行了。
如下:
Dumping objects ->
E:/Develop/test/console/console.cpp(20) : {50} normal block at 0x003F0F70, 20 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
d:/program files/microsoft visual studio/vc98/include/crtdbg.h(552) : {49} normal block at 0x003F0F28, 10 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD
Object dump complete.
【下面想写的是ACE中设置线程堆栈相关的,缘起AIX默认堆栈太小】