处理内存泄漏的一种MFC方法

原创 2000年11月28日 07:16:00

 

信息产业部数据所
廖 铮

---- Windows使用复杂的内存管理器控制和优化内存的使用(包括磁盘缓冲)。一旦内存管理出现纰漏就会导致内存泄漏。内存泄漏的实质一般是因为在堆上分配了某块内存但以后不再对其重新分配,使得该部分内存失去重用性。出现这一问题的多数应用程序一开始往往正常运行,所以要检测出该类问题是较为困难的。不过,要将其找出并得到正确的处理才更麻烦。大多数MFC应用程序允许Windows安全地管理分配给资源的内存,如果分配内存的组件不由系统所处理的话内存泄漏的危险就大大增加了。这里通过举例来讨论一些相关的问题。

示例:多次重绘窗口导致内存泄漏

---- 我们简单建立一个STD的MFC工程MLeak,该程序首先创建逻辑字体,随后TextOut() 函数在窗口的客户区书写文本,如果程序类似图1(略)左那样持续再长时间你也看不到会出现什么奇怪的现象。但你用鼠标抓住窗口的边界改变窗口大小多次(多的时候要到数十次)就会看见窗口变成了图1右那样:字体出问题了。TextOut()函数仍然可以在窗口上书写文本,但是逻辑字体却没有得到正确的创建。一般会认为问题出在OnDraw()函数内的字体创建过程中。真是这样吗?

查找和分析问题

---- 幸好有些MFC类和函数可以用于发现内存泄漏。添加相应代码就有助于检查CMLeakView类中存在的内存泄漏问题(关键的代码以粗体标识)。首先我们用ClassWizard为视图类加入 OnCreate() 函数,目的是为了在程序初始化时获得堆的有关统计数据。只要调用oldMemState.Checkpoint()函数即可做到这一点。接着OnDraw()函数内在完成与字体有关的全部工作后将执行以下附加的调试代码:

#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。看来,分配给字符串的内存在屏幕每次重绘时被重新分配了。

内存诊断参数

---- 启用或禁用内存诊断可以调用全局函数AfxEnableMemoryTracking()。Debugger将自动地控制它,所以该函数作为开关函数将显著增加程序执行速度并减少诊断信息。MFC全局变量afxMemDF则使得特定内存诊断特性可用。该变量信息可以查阅相关资料。

查找内存泄漏

---- 我们首先实现一个CMemoryState对象(CMemoryState的使用可参看有关资料)。在输入有问题代码之前调用Checkpoint()函数 来获得内存使用的原始情况。然后实现另一个CMemoryState对象并在写完有问题代码之后调用Checkpoint()函数来得到内存使用后的情况。当然,还可以实现第三个CMemoryState对象并调用Difference()成员函数。调用该函数时用先前的两个CMemoryState对象作为其参数。如果内存前后没有差异则函数返回值非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

内存状况统计

---- CMemoryState() 成员函数可用于得到当前内存的统计资料或者两个内存对象状态的差异。此外还可用于查找堆上内存泄漏。以下代码使用了原始信息来检测当前的内存状态:

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 时就会这样。第二行用于指示多少对象还存在于堆上。第三行指示多少非对象块(新分配的)被分配并且没有被释放。第四行指示应用程序在给定时间内使用的最大内存。最后一行指示工程使用的全部内存。以上任何一行出现问题都意味着内存泄漏了。

修复工程

---- 虽然在CMLeakView类中适当处理OnDraw()中的字符串也可能成功解决先前问题,不过AppWizard已经创建了负责存储和分配工作的专门类CMLeakDoc文档类。 我们可以将要显示的字符串在MLeakDoc.h文件中声明为CMLeakDoc的成员变量:

CString myCString;
   
然后在在CMLeakDoc的构造函数中对其赋值:

CMLeakDoc::CMLeakDoc()
{
myCString = "This program doesn't have a leak";
}

最后修复的工程文件大致如下所示:

// MLeakView.cpp :
implementation of the CMLeakView 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;
}
---- 以上的一些技术性的手段可以使程序员对一些很隐蔽的内存陷阱有一些新的认识,不过,发现并能解决内存泄漏问题始终是个需要耐心和细心的过程,经验或许会更重于技术指南。

《人工智能:一种现代的方法》总结 第二章

第二章 智能Agent 这一章主要讨论了Agent的各个方面,给读者一个初步的了解。...
  • AchildFromBUAA
  • AchildFromBUAA
  • 2017年03月27日 22:56
  • 511

MFC框架的一种理解方法

MFC的水很深,如果你想搞清楚所有的的细枝末节,恐怕我这里就帮不到你了,需要看侯捷的《深入浅出MFC》之类的书籍,一般而言,我们没必要知道所有的底层技术,但是知道一点基本的框架,对于编程还是很有用的。...
  • q__y__L
  • q__y__L
  • 2016年04月27日 00:25
  • 2278

VS2010/MFC编程入门之九(对话框:为控件添加消息处理函数)

本文转自鸡啄米:http://www.jizhuomi.com/software/156.html      感 谢鸡啄米对本文的贡献。    创建对话框类和添加控件变量在上一讲中已经讲过,这一...
  • my_lord_
  • my_lord_
  • 2016年08月12日 11:31
  • 1411

如何安全删除基于对话框的MFC中控件的事件处理程序

如上图,要删除的为“启动服务”按钮控件,其ID为 IDC_START,此控件已存在一个事件处理程序,先打算将其删除。 共需删除三个地方的代码。 第一,事件处理程序的原代码。 ...
  • lwqhuiji
  • lwqhuiji
  • 2015年10月28日 10:51
  • 1201

MFC学习之路01 快捷键的生成方法

快捷键一般都是使用在CMenu中,所以有的Menu创建过程(此处省略)。 1、切换到资源视图->添加资源->Acclerator 2、在ID中选择需要添加快捷键的控件ID,修饰符中选择Ctrl等,...
  • t1234xy4
  • t1234xy4
  • 2015年07月16日 17:14
  • 919

MFC的消息处理函数

转自:http://blog.163.com/chen_xinghuan/blog/static/172201582201151192943481/1.AfxWndProc() 该函数负责接收消息...
  • majinlei121
  • majinlei121
  • 2015年05月21日 08:36
  • 1691

OpenCV与MFC通用型图像处理开发实践

imagehandler   该程序的起源说来话长,它起源自上课的一次作业,后来又由于接触了OpenCV这个开源库,我就试图将OpenCV和MFC搓到一块,毕竟微软近来在界面上也下了狠功夫,尤其是Wi...
  • giantchen547792075
  • giantchen547792075
  • 2014年04月30日 23:15
  • 5773

数字图像处理MFC程序设计之图像的打开显示

最近想把以前做过的数字图像处理实习写成一个教程,用的是CBmpFile类,武汉大学遥感信息工程学院的学生也许都会用这个类来实现图像处理,废话就不多说了,直接开始吧。。。     一、图像的显示    ...
  • whustyle
  • whustyle
  • 2014年12月10日 22:11
  • 1041

Segment fault的一种处理方法

处理segmnet fault
  • u010642238
  • u010642238
  • 2016年05月06日 09:40
  • 77

一种优秀的异常处理方法

摘要 本发明涉及JAVA技术WEB开发领域,具涉及一种优秀的异常处理方法。本方法当平台代码抛出异常时,将异常转化为RuntimeException类型的异常类A,但不作任何处理,而是再向外抛送;...
  • dongwenkai365506
  • dongwenkai365506
  • 2016年03月26日 14:02
  • 256
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:处理内存泄漏的一种MFC方法
举报原因:
原因补充:

(最多只允许输入30个字)