MFC内存泄露检测

这几天一直在检查程序内存泄露的问题,今天终于告一段落。
内存泄露在编制小型应用程序时看不出其危害,但如果是编制24小时运行的大型平台应用程序时,如果有内存泄露,则随着程序的运行,其占用的内存会越来越多,最终导致系统崩溃。因此,内存泄露不容小觑。下面是几天来检查内存泄露的一点经验小结。

1.常规内存泄露的检测
常规内存泄露一般是由于编程者在手动申请内存空间之后没有释放造成的。如用new、malloc()函数或CString的GetBuuffer()函数申请内存空间后没有使用对应的释放语句释放内存空间。
(1)大范围的内存泄露检测
_CrtDumpMemoryLeaks()函数就是显示 当前的内存泄漏。注意是“当前”,也就是说当它执行时,所有未销毁的对象均会报内存泄漏,因此尽量让这条语句在程序的最后执行。用法如下:
在StdAfx.h中添加如下代码:

#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include<stdlib.h>
#include<crtdbg.h>
#endif 

在需要检测当前未销毁内存的位置添加如下代码:

_CrtDumpMemoryLeaks();

则在debug模式下,程序运行到上述代码时,在debug的输出框会出现类似如下信息:

Detected memory leaks!
    Dumping objects ->
    D:\...\*****.cpp(1463) : {8244} normal block at 0x01AA84B8, 512 bytes long.
    Data: <                > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

上述信息提示检测到内存泄露发生的具体的文件名及行号,后面则给出了内存泄露的标号、大小及内容。
(2)代码块的内存泄露检测
_CrtMemCheckpoint()、_CrtMemDifference()、_CrtMemDumpStatistics()函数可以实现代码块在执行前后的内存比较,从而实现对内存泄露语句的精确定位。用法如下:

_CrtMemState s1, s2, s3;
    ...
    _CrtMemCheckpoint(&s1);
    ...(需要检测的代码块)
   
 _CrtMemCheckpoint(&s2);
    if(_CrtMemDifference(&s3, &s1, &s2))
        _CrtMemDumpStatistics(&s3);

这段代码在执行时,在_CrtMemCheckpoint(&s1)位置会保存一份当前的内存快照,_CrtMemCheckpoint(&s2)位置又会保存一份当前内存的快照,_CrtMemDifference(&s3, &s1, &s2)则会比较s1与s2内存快照的区别,并存入s3,如果发生内存泄露,_CrtMemDifference(&s3, &s1, &s2)会返回真值。_CrtMemDumpStatistics(&s3)则可以dump出内存泄露信息:

0 bytes in 0 Free Blocks.
    75 bytes in 3 Normal Blocks.
    5037 bytes in 41 CRT Blocks.
    0 bytes in 0 Ignore Blocks.
    0 bytes in 0 Client Blocks.
    Largest number used: 5308 bytes.
    Total allocations: 7559 bytes. 

如果该代码块没有发生内存泄露,则不会出现上述信息。

2.第三方库内存泄露检测
有时候内存泄露并不是由于程序员未释放申请的内存空间,而是由于第三方库函数造成的内存泄露,这种内存泄露用常规方法很难发现。这时,可以先根据经验确定是哪一块代码发生了内存泄露,然后注释掉这部分代码及其之后的代码,运行程序,同时在“任务管理器”或其他内存管理工具中观察该程序运行时的内存变化情况,如果随时间增加,内存不再显著增加,则可确定发生内存泄露的位置应该在注释掉的代码中。然后去掉该段代码的注释,如果在“任务管理器”中该程序又出现明显的内存泄露,则可确定该段代码出现了内存泄露。通过这种方法,对程序逐块检验,逐层深入,就可以最终精确定位发生内存泄露的语句。
如果出现这种内存泄露,先检查是否是自己对该第三方库的使用不当造成的内存泄露,如果确定不是,则证明该第三方库的函数有缺陷,应向该库的开发者反映改进并换用其他途径解决该段代码设计问题。

3.多线程造成的内存泄露检测
多线程造成的内存泄露最难以检测,它通常是由于内存空间的申请和释放线程不同步造成的。举个例子:
假设在一个函数中有如下代码:

void ***::DisplayChart(...)
    {
        ...
        x=new double[100*100];
        y=new double[100*100];
        z=new double[100*100];
        ...
        Invalidate();
    }

在OnPaint函数中有如下代码:

void ***::OnPaint()
    {
        ...
        delete[] x;
        delete[] y;
        delete[] z;
        ...
    }

Invalidate()函数执行时会发送消息WM_PAINT,从而会导致OnPaint()函数的执行,这样看似没问题,但其实仍有可能造成内存泄露。
因为Invalidate()函数执行时只是发送WM_PAINT消息到消息队列,到OnPaint()函数的执行还有一段时间,在这个时间间隔中如果主程序再次调用DisplayChart()函数,则x、y、z的值会被新申请的内存空间的地址刷新,而原来OnPaint()函数中的delete还没来得及执行,这样就造成了内存泄露。
这种内存泄露问题很隐蔽,只能先找到发生内存泄露的大致位置,然后凭经验去分析出现内存泄露的原因,从而解决问题。
这种内存泄露的解决办法就是在多线程时加入一些线程同步和等待的代码,确保申请的内存空间能及时的释放。

猜你喜欢

转载自blog.csdn.net/hk121/article/details/80223654
今日推荐