信息产业部数据所多媒体室 廖 铮
Windows使用复杂的内存管理器控制优化内存的使用(包括磁盘缓冲)。一旦内存管理出现纰漏就会导致内存泄漏。内存泄漏的实质是因为在堆上分配了某块内存但以后不再对其重新分配,使得该部分内存失去重用性。出现这一问题的多数应用程序一开始往往正常运行,所以要检测出该类问题是较为困难的,要将其找出并得到正确的处理更麻烦。大多数MFC应用程序允许Windows安全地管理分配给资源的内存,如果分配内存的组件不由系统所处理的话,内存泄漏的危险就大大增加了。这里通过举例来讨论一些相关的问题。
#ifdef _DEBUG
newMemState.Checkpoint();
if(diffMemState.Difference(oldMemState,newMemState)) {
TRACE(“Difference between first and now!\n\n");
diffMemState.DumpStatistics();
}
#endif
调用newMemState.Checkpoint() 将获得堆的最新情况, diffMemState.Difference()则在原始值和当前值出现差异时返回信息。统计结果通过调用 diffMemState.DumpStatistics()被扔出。因为该信息包含在OnDraw()函数内,而OnDraw()函数响应WM_PAINT消息重绘屏幕窗口,则在每次改变窗口大小时将打印出统计结果,我们发现每次公布的统计数据的最后一行才有变化:
Difference between first and now!
(第一次统计信息的开始行)
... ...
Total allocations: 87 bytes.(第一次统计信息的结束行)
......
Total allocations: 132 bytes.(第二次统计信息的结束行)
... ...
Total allocations: 14352 bytes.(最后一次统计信息的结束行)
可以注意到每次重绘屏幕都导致整个分配区在增加,增加幅度为45字节,重绘一定次数后内存分配就到达了14,352字节。那么会不会是忘了为逻辑字体结构分配内存呢?我们再向OnDraw()函数中插入以下粗体代码:
LOGFONT lf;
... ...
memset(&lf,0,sizeof(LOGFONT));
... ...
结果如故,说明逻辑字体结构大小并没有与此发生必然联系。从OnDraw()函数中去掉字体创建过程并加入到OnCreate()中,使逻辑字体资源在创建窗口时得到创建,不过还是可以发现分配的整个内存仍然持续增加!于是修改OnDraw() 如下:
void CMLeakView::OnDraw(CDC* pDC)
{
CMLeakDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC->TextOut(20, 200,
“This program has memory problems");
#ifdef _DEBUG
newMemState.Checkpoint();
if(diffMemState.Difference(oldMemState, newMemState)) {
TRACE(“Difference between first and now!\n\n");
diffMemState.DumpStatistics();
}
#endif
}
问题仍然出现,而以下代码是OnDraw()中所增加的唯一代码:
pDC->TextOut(20, 200, “This program has memory problems");
将该行代码注释掉并重新编译、运行诊断程序,可以发现整个内存分配统计结果增幅为0。看来,分配给字符串的内存在屏幕每次重绘时被重新分配了。
#ifdef _DEBUG
CMemoryState oldMemState, newMemState, diffMemState;
oldMemState.Checkpoint();
#endif
...
(被测试的代码)
...
#ifdef _DEBUG
newMemState.Checkpoint();
if(diffMemState.Difference(oldMemState, newMemState)) {
TRACE(“Memory Leaked Here:\n\n" );
}
#endif
TRACE(“Current Memory Picture:\n\n" );
NewMemState.DumpStatistics();
很容易获取先后内存状态的差异:
if( diffMemState.Difference(oldMemState,newMemState))
{
TRACE( “Memory Leaked Here:\n\n");
diffMemState.DumpStatistics();
}
diffMemState.DumpStatistics()的示例输出如下:
0 bytes in 0 Free Blocks
2 bytes in 1 Object Blocks
50 bytes in 5 Non-Object Blocks
Largest number used: 76 bytes
Total allocations: 304 bytes
以上代码第一行指示延迟释放的内存块数目。当 afxMemDF 变量设置为delayFreeMemDF 时就会这样,第二行用于指示多少对象还存在于堆上,第三行指示多少非对象块(新分配的)被分配并且没有被释放,第四行指示应用程序在给定时间内使用的最大内存,最后一行指示工程使用的全部内存。以上任何一行出现问题都意味着内存泄漏了。
CString myCString;
然后在CMLeakDoc的构造函数中对其赋值:
CMLeakDoc::CMLeakDoc()
{
myCString = “This program doesn't have a leak";
}
最后修复的工程文件大致如下所示:
// MLeakView.cpp : implementation of the CM
LeakView class
//
... ...
CFont NFont;
... ...
void CMLeakView::OnDraw(CDC* pDC)
{
... ...
CFont* pOFont;
pOFont = pDC->SelectObject(&NFont);
pDC->TextOut(20, 200, pDoc->myCString);
DeleteObject(pOFont);
}
... ...
int CMLeakView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
LOGFONT lf;
memset(&lf,0,sizeof(LOGFONT));
lf.lfHeight = 50;
lf.lfWeight=FW_NORMAL;
lf.lfEscapement=0;
lf.lfOrientation=0;
lf.lfItalic=false;
lf.lfUnderline = false;
lf.lfStrikeOut = false;
lf.lfCharSet=ANSI_CHARSET;
lf.lfPitchAndFamily=34; //Arial
NFont.CreateFontIndirect(&lf);
return 0;
}
以上的一些技术性的手段可以使程序员对一些很隐蔽的内存陷阱有一些新的认识。不过,发现并能解决内存泄漏问题始终是个需要耐心和细心的过程,经验或许会更重于技术指南。