注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

多多的爹

 
 
 

日志

 
 

MFC捕获内存泄漏内幕  

2007-07-27 17:05:21|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

在MFC中,DEBUG的配置环境下,程序运行后,如果有内存泄漏,通常会在输出窗口显示出来,比如下面这段文字:
Detected memory leaks!
Dumping objects ->
f:\test05\test05\test05.cpp(38) : {112} normal block at 0x003AC3D0, 1 bytes long.
 Data: < > CD
Object dump complete.

这是怎么实现的呢?以下就是分析过程。
在MFC中,几乎每个Cpp文件的开始,都有以下一段代码:
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
在define中,将new定义成DEBUG_NEW——一个MFC提供的调试用的new函数。这样在DEBUG版本中,当我们使用new操作申请内存时,实际上进入的是DEBUG_NEW。
再看看DEBUG_NEW的定义:
void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__)
虽然我也不明白这个define的含义,但是通过调用堆栈,我们可以看到,实际上DEBUG_NEW是指向了上面那个函数。
以下是一个new操作的调用过程:
void* __cdecl operator new(size_t nSize, LPCSTR lpszFileName, int nLine)
 void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine) //nType=1
  void * __cdecl _malloc_dbg (size_t nSize, int nBlockUse, const char * szFileName, int nLine) //nBlockUse=1
   void * __cdecl _nh_malloc_dbg (size_t nSize,int nhFlag,int nBlockUse,const char * szFileName,int nLine) //nhFlag=0
    void * __cdecl _heap_alloc_dbg(size_t nSize,int nBlockUse,const char * szFileName,int nLine)
     void * __cdecl _heap_alloc(size_t size)

在_heap_alloc中我们看到了内存申请的真面目:
return HeapAlloc(_crtheap, 0, size ? size : 1);
真是简单啊!
其实不是,真正有内容的东西,都在_heap_alloc_dbg这个函数中,我们看看这个函数的伪码:
void * __cdecl _heap_alloc_dbg(size_t nSize,int nBlockUse,const char * szFileName,int nLine)
{
 _CrtMemBlockHeader * pHead;
 if (nSize < (size_t)(_HEAP_MAXREQ - nNoMansLandSize - sizeof(_CrtMemBlockHeader)))
 {
  blockSize = sizeof(_CrtMemBlockHeader) + nSize + nNoMansLandSize;
  pHead = (_CrtMemBlockHeader *)_heap_alloc_base(blockSize);
  if (pHead)
  {
   if (_pFirstBlock)
                         _pFirstBlock->pBlockHeaderPrev = pHead;
                 else
                         _pLastBlock = pHead

   /* fill in gap before and after real block */
                        memset((void *)pHead->gap, _bNoMansLandFill, nNoMansLandSize);
                        memset((void *)(pbData(pHead) + nSize), _bNoMansLandFill, nNoMansLandSize);
                        /* fill data with silly value (but non-zero) */
                        memset((void *)pbData(pHead), _bCleanLandFill, nSize);;

   retval=(void *)pbData(pHead);
  }
 }
}
这个函数做了4件事情:
1.申请一块内存。注意到,MFC实际上申请的内存比我们的请求大了不少,其增加的容量为sizeof(_CrtMemBlockHeader) + nNoMansLandSize。结构体_CrtMemBlockHeader记录此段内存的信息以及做为后面所说的链表的信息。nNoMansLandSize的内容是0xFD,填充内存的后一段,可能是防止溢出的。
2.将此块内存放到一个全局的链表_pFirstBlock中。通过此链表,可以找到所有申请过的内存。
3.初始化内存。内存被分成三段,第一段是_CrtMemBlockHeader结构,主要是链表信息。第二段是给用户的内存,DEBUG下被初始化成0xCD。第三段大小是nNoMansLandSize,初始化为0xFD。
4.最后是返回被请求的内存,这里用到一个宏pbData,我们看看:
#define pbData(pblock) ((unsigned char *)((_CrtMemBlockHeader *)pblock + 1))
实际上是将申请的内存的_CrtMemBlockHeader后的一段数据返回去了。

通过这段代码,我们可以猜测到,MFC实际是用了一个全局链表将所有申请的内存块都连在了一起,当删除的时候,从链表中删掉,然后将内存回收。
OK,再来看看delete的调用过程,印证我们的猜测:
void __cdecl operator delete(void* p)
 void __cdecl _free_dbg(void * pUserData, int nBlockUse) //nBlockUse=0x00000001
  void __cdecl _free_base(void * pBlock)
_free_base和_heap_alloc一样简单,只有一句话:
retval = HeapFree(_crtheap, 0, pBlock);
真正的东西,还是在_free_dbg中,再看它的伪代码:
void __cdecl _free_dbg(void * pUserData, int nBlockUse)
{
 pHead = pHdr(pUserData);
 if (pHead->pBlockHeaderNext)
  pHead->pBlockHeaderNext->pBlockHeaderPrev = pHead->pBlockHeaderPrev;
 else
  _pLastBlock = pHead->pBlockHeaderPrev;
 if (pHead->pBlockHeaderPrev)
  pHead->pBlockHeaderPrev->pBlockHeaderNext = pHead->pBlockHeaderNext;
 else
  _pFirstBlock = pHead->pBlockHeaderNext;
 memset(pHead, _bDeadLandFill, sizeof(_CrtMemBlockHeader) + pHead->nDataSize + nNoMansLandSize);
        _free_base(pHead);
}
pHdr是pbData的逆过程:
#define pHdr(pbData) (((_CrtMemBlockHeader *)pbData)-1)
然后把这段内存从链表中拿出来,把内存设置成0xDD,最后删掉内存。

最后,当程序退出的时候,会调用函数_CrtDumpMemoryLeak检查内存的释放情况。
_CrtDumpMemoryLeak首先调用_CrtMemCheckpoint获得内存释放情况,如果还有内存没有释放,则调用_CrtMemDumpAllObjectsSince报告,_CrtMemDumpAllObjectsSince又调用_CrtMemDumpAllObjectsSince_stat。最后输出了文章最开始的那段文字。
_CrtMemCheckpoint的核心内容就是检查全局变量_pFirstBlock,通过一个for循环,将_pFirstBlock中没有被释放的内存信息放到一个_CrtMemState结构中。

 
  评论这张
 
阅读(53)| 评论(9)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017