文丨朴艳丽 张 楠
C++的强大功能之一就是可以进行动态内存分配,即允许在程序运行的过程中灵活分配所需的内存,然而,有效地管理这些内存同样也是非常重要的。当以前分配的一片内存不再需要使用或无法访问时,但是却并没有释放它,那么对于该进程来说,会因此导致总可用内存的减少,这时就出现了内存泄漏。
一般我们常说的内存泄露指的是堆内存的泄露。应用程序一般以malloc,new,relloc,等从内存中分配的内存空间,使用完成后程序必须负责free或者delete掉这部分内存空间,否则这块内存就不能被再次使用就是我们通常说的内存泄露。此外,内存泄露还包括系统资源的泄露。比如HANDLE,GDI Object,Socket,interface等,有些对象消耗的是核心态的内存,因此,从某种角度上说,系统资源的泄露比堆内存泄露更加恐怖,可能导致操作系统的不稳定。GDI object泄露是一种常见的资源泄露。
以发生的方式来说,内存泄露大致分为以下几种:(1)常发性内存泄露,发生内存泄露的代码会被经常执行到,每次执行都会导致内存泄露。(2)偶发性内存泄露,发生内存泄露的代码只有在特定的环境下才会被执行到,因此,在特定的环境下偶发内存泄露可能会变成常发性内存泄露,检测内存泄露变得至关重要。(3)一次性内存泄露,发生内存泄露的代码只会被执行一次。(4)隐式内存泄露。程序在运行过程中不停的分配内存,但是直到结束后才释放内存,从严格的角度来说这里并没有内存泄露,因为程序最终释放了所有的申请资源,但是不及时的释放内存也有可能造成内存泄露。
每当分配一块内存,就把它的指针加入一个全局的list中,每当释放一块内存就把它的指针从全局list中取出,最后等到进程关闭的时候,list中剩余的指针就是那些没有释放的内存。
如果要检测内存泄露,那么只要截取住malloc/relloc/free和new/delete即可,其实(new/delete)也是malloc free 所以只要截获前一组即可,对于其他的内存泄露采用类似的方法,截获住相应的分配和释放函数。比如检测BSTR的泄露,就需要截获SysallocString/SysfreeString,要检测HMENU的泄露,就需要截获CreateMenuDestoryMenu(有的资源分配函数由多个但是释放函数只有一个,此时需要截获所有的分配函数)。
要使除错函数生效,必须要在程序中包含以下几个语句:
并且这些#include 语句必须按上边给出的顺序使用。如果你改变了顺序,可能导致使用的函数工作不正常。包含crtdbg.h的作用是用malloc和free函数的debug版本(_malloc_dbg 和 _free_dbg)来替换他们,他们能跟踪内存分配和回收。这个替换仅仅是在debug状态下生效,Relese版本中还是使用普通的malloc和free函数。上面的#define语句使用crt堆函数相应的debug版本来替换正常的堆函数。这个语句不是必需的,但是没有他,你可能会失去一些有用的内存泄漏信息。
一旦在程序中增加了以上的语句,就可以通过在程序中增加_CrtDumpMemoryLeaks();函数来输出内存泄漏信息。当你在debuger下运行你的程序时,_CrtDumpMemoryLeaks 显示内存泄漏信息在OutPut窗口的Debug标签项里。
一般有三种:(1)MS C-Runtime libray内建的检测功能;(2)外挂是检测工具,诸如purify,boundschecker等;(3)利用windows NT自带的performance Monitor。
BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误。它通过驻留在集成开发环境内部的自动处理调试程序来加速应用程序的开发,缩短产品发布时间。BoundsChecker对于编程中的错误提供了清晰的详细的分析。它能够检测和诊断出,在静态堆栈内存中的错误以及内存和资源泄漏问题。在集成开发环境下,调试运行DEBUG版程序,BoundsChecker在运行时检测内存泄漏,并在可能出现内存泄漏的代码处中断程序运行,开发人员可根据调用现场状态,排除内存泄漏。
调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。
首先,需要在程序中包含必要的语句,用来启用调试堆函数。其次,设置内存泄漏检测报告。在程序结束后,自动调用_CrtDumpMemoryLeaks方法,在OUTPUT窗口中报告内存泄漏的相关信息。最后,根据OUTPUT窗口中提示的内存泄漏相关信息,排除泄漏。分两种情况:一种情况是程序退出时,在OUTPUT窗口中,直接报告出现内存泄漏的源代码文件名及具体代码行数。只需要分析此处代码,根据上、下文修改,一般就可以正确释放内存了;另外一种情况是使用_CrtSetBreakAlloc方法来检查定位内存泄漏位置。
具体操作步骤如下:(1)先在调试状态下运行几次程序,观察内存分配顺序号是哪几个值。(2)用出现次数最多的那个顺序号来设断点。即:在代码中添加如下调用:_CrtSetBreakAlloc(20);(假设:OUTPUT窗口中,报告{20}最多。即:第20次内存分配出现泄漏的情况较常发生)(3)在调试状态下运行程序,在断点停下时,打开"调用堆栈"窗口,找到对应发生内存泄漏的源代码。(4)退出程序,观察OUTPUT窗口的内存泄漏报告,看本次内存分配的顺序号是不是和预设值(_CrtSetBreakAlloc中设置的值)相同,如果相同,就找到了;如果不同,就重复步骤(3),直到相同。(5)最后根据分析结果,在适当的位置释放分配的内存。
由于自己在教学实践及编程经验上还有很多不足,上文中的错误及不足之处希望大家多多批评、指正。
[1][C++2010]ISO/IEC 14882.Programming Languages-C++,1st Edition. International Standardization Organization,International Electrotechnical Commission,American National Standards Institute,and Information Technology Industry Council,2010.
[2][Stroustrup2009]Bjarne Stroustrup. The C++ Programming Language,3rd Edition. Addison-Wesley,2009.