用Crt系列函数分析windows程序的内存泄露

来源:互联网 发布:汉诺塔递归算法机制 编辑:程序博客网 时间:2024/06/13 02:23

近几日分析了windows版本的一个内存泄露故障,之前之后补了下功课,记录如下,抽时间分了近10次才补充完整,可能不太连贯,只是对所用知识的一个提示,欲知详情还是msdn等来的实在。

 

本文用到的几个很重要的对内存分配相关的结构定义:

[cpp] view plain copy
  1. typedef struct _CrtMemState  
  2. {  
  3.         struct _CrtMemBlockHeader * pBlockHeader;  
  4.         unsigned long lCounts[_MAX_BLOCKS]; //5中BLOCK类型的计数器  
  5.         unsigned long lSizes[_MAX_BLOCKS];//每个BLOCK类型分配的总的字节数  
  6.         unsigned long lHighWaterCount;// The most bytes allocated at a time up to now:  
  7.         unsigned long lTotalCount;目前分配的总的字节数  
  8. } _CrtMemState;  
  9.   
  10. <dbgint.h>  
  11. #define nNoMansLandSize 4  
  12.   
  13. typedef struct _CrtMemBlockHeader  
  14. {  
  15.         struct _CrtMemBlockHeader * pBlockHeaderNext;//指向上一个分配的内存块  
  16.         struct _CrtMemBlockHeader * pBlockHeaderPrev;//指向随后分配的内存块  
  17.         char *                      szFileName;//本块分配时的文件名  
  18.         int                         nLine;所在文件行数  
  19.         size_t                      nDataSize;  
  20.         int                         nBlockUse;  //Block type:_CLIENT_TYPE, _NORMAL_TYPE等  
  21.         long                        lRequest;   //分配请求的次数,第几次  
  22.         unsigned char               gap[nNoMansLandSize];//本次分配的内存块之前的内存区域  
  23.         /* followed by: 
  24.          *  unsigned char           data[nDataSize]; 
  25.          *  unsigned char           anotherGap[nNoMansLandSize]; 
  26.          */  
  27. } _CrtMemBlockHeader;  

 

所分配内存两侧的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: 

[cpp] view plain copy
  1. /*** 
  2. *_CrtMemState * _CrtMemStateCheckpoint() - checkpoint current memory state 
  3. * 
  4. *Purpose: 
  5. *       checkpoint current memory state 
  6. * 
  7. *Entry: 
  8. *       _CrtMemState * state - state structure to fill in, will be 
  9. *       allocated if NULL 
  10. * 
  11. *Return: 
  12. *       current memory state 
  13. * 
  14. *******************************************************************************/  
  15. _CRTIMP void __cdecl _CrtMemCheckpoint(_CrtMemState * state)  

在_CrtMemState 结构中保存堆的快照,_CrtmemState类型变量有程序定义,此处接受的是该变量地址。 
如_CrtMemState tMS1;
_CrtMemCheckpoint(&tMS1);

【2】_CrtMemDifference: 

[cpp] view plain copy
  1. /*** 
  2. *int _CrtMemDifference() - compare two memory states 
  3. * 
  4. *Purpose: 
  5. *       compare two memory states 
  6. * 
  7. *Entry: 
  8. *       _CrtMemState * stateDiff - return memory state difference 
  9. *       _CrtMemState * oldState - earlier memory state 
  10. *       _CrtMemState * newState - later memory state 
  11. * 
  12. *Return: 
  13. *       TRUE if difference 
  14. *       FALSE otherwise 
  15. * 
  16. *******************************************************************************/  
  17. int _CrtMemDifference( _CrtMemState *stateDiff, const _CrtMemState *oldState, const _CrtMemState *newState );  

比较两个内存状态结构oldState与newState,在第三个状态结构stateDiff中保存二者之间的差异,如果两个状态不同,则返回 TRUE。

 

【3】
_CrtMemDumpStatistics: 
转储给定的 _CrtMemState 结构。该结构可能包含给定_CrtMemState结构即堆状态的快照或两个快照之间的差异。

 

【4】
_CrtMemDumpAllObjectsSince:

[cpp] view plain copy
  1. /*** 
  2. *void _CrtMemDumpAllObjectsSince() - dump all objects since memory state 
  3. * 
  4. *Purpose: 
  5. *       dump all objects since memory state 
  6. * 
  7. *Entry: 
  8. *       _CrtMemState * state - dump since this state 
  9. * 
  10. *Return: 
  11. *       void 
  12. * 
  13. *******************************************************************************/  
  14. _CRTIMP void __cdecl _CrtMemDumpAllObjectsSince(const _CrtMemState * state)   

转储相对于给定的内存状态以来或从执行开始以来所分配的所有对象的信息。如果已经使用 _CrtSetDumpClient 安装了挂钩函数,那么,_CrtMemDumpAllObjectsSince 每次转储 _CLIENT_BLOCK 块时,都会调用应用程序所提供的挂钩函数。
下面的_CrtDumpMemoryLeaks的函数实现贴在下面了,该函数对于检查程序生命周期的内存泄露很有用处,看起实现无非是从NULL开始到现在的内存快照还有未释放的内存块时,调用下本函数,其中给定的内存状态为NULL,表示从程序最开始以来,因而_CrtDumpMemoryLeaks也应该在函数最后结束的出口处调用才对,否则容易误报。

对于我们目前的应用系统启动过程中初始化配置等会申请大量内存,之后正常情况应该是稳定运行,不该结束,因而想要检测内存泄露,可以在确认程序启动后坐下内存快照,然后运行之中定时或定量(即处理了多少次接入请求之后)去做检测。
检测报告应该相对于最开始的内存快照进行,理论上如果存在内存泄露应该在多次输出的文件中都应该看到该次内存块的分配才对。如果只是在某一两次输出能看到该内存块为释放,而随后的输出中就没了则说明只是迟了一点,也不算内存泄露。

 

【5】
_CrtDumpMemoryLeaks :

[cpp] view plain copy
  1. /*** 
  2. *void _CrtMemDumpMemoryLeaks() - dump all objects still in heap 
  3. * 
  4. *Purpose: 
  5. *       dump all objects still in heap. used to detect memory leaks over the 
  6. *       life of a program 
  7. * 
  8. *Entry: 
  9. *       void 
  10. * 
  11. *Return: 
  12. *       TRUE if memory leaks 
  13. *       FALSE otherwise 
  14. * 
  15. *******************************************************************************/  
  16. _CRTIMP int __cdecl _CrtDumpMemoryLeaks(void)  
  17. {  
  18.         /* only dump leaks when there are in fact leaks */  
  19.         _CrtMemState msNow;  
  20.   
  21.         _CrtMemCheckpoint(&msNow);  
  22.   
  23.         if (msNow.lCounts[_CLIENT_BLOCK] != 0 ||  
  24.             msNow.lCounts[_NORMAL_BLOCK] != 0 ||  
  25.             (_crtDbgFlag & _CRTDBG_CHECK_CRT_DF &&  
  26.             msNow.lCounts[_CRT_BLOCK] != 0)  
  27.            )  
  28.         {  
  29.             /* difference detected: dump objects since start. */  
  30.             _RPT0(_CRT_WARN, "Detected memory leaks!/n");  
  31.   
  32.             _CrtMemDumpAllObjectsSince(NULL);  
  33.             return TRUE;  
  34.         }  
  35.   
  36.         return FALSE;   /* no leaked objects */  
  37. }  

确定自程序开始执行以来是否发生过内存泄漏,如果发生过,则转储所有已分配对象。如果已使用 _CrtSetDumpClient 安装了挂钩函数,那么,_CrtDumpMemoryLeaks 每次转储 _CLIENT_BLOCK 块时,都会调用应用程序所提供的挂钩函数。

 

 

 

最后给出一个非常短小的vc代码的例子


[cpp] view plain copy
  1. #include <stdlib.h>  
  2. #include <crtdbg.h>  
  3.   
  4. #ifdef WIN32  
  5. #ifdef _DEBUG  
  6. //#define _CRTDBG_MAP_ALLOC  
  7. #define new new( _NORMAL_BLOCK, __FILE__, __LINE__)  
  8. #define malloc(n) _malloc_dbg(n, _NORMAL_BLOCK, __FILE__, __LINE__)  
  9. #endif   
  10. #endif  
  11.   
  12.   
  13. int main (int argc, char *argv[])  
  14. {  
  15.     _CrtMemState tMS1;  
  16.     _CrtMemCheckpoint(&tMS1);  
  17.     char *p = new char[10];  
  18.     char *q = (char *)malloc(20);  
  19.   
  20.     _CrtMemDumpAllObjectsSince(&tMS1);  
  21.     return 0;  
  22. }  

输出:
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.

原创粉丝点击