建议32:借助工具监测内存泄漏问题(1)
内存管理确实是一个令众多C/C++(www.cppentry.com)程序员感到费神又费力的问题,内存错误通常都具有隐蔽性,难以再现,而且其症状一般不能在相应的源代码中找到。C/C++(www.cppentry.com)应用程序的大部分缺陷和错误都和内存相关,预防、发现、消除代码中和内存相关的缺陷,成为C/C++(www.cppentry.com)程序员编写、调试、维护代码时的重要任务。然而任何人都无法时刻高度谨慎,百密中难免会有一疏,一不小心就会发生内存问题。如果泄漏内存,则运行速度会逐渐变慢,并最终会停止运行;如果覆盖内存,则程序会变得非常脆弱,很容易受到恶意用户的攻击。因此,需要特别关注C/C++(www.cppentry.com)编程(www.cppentry.com)的内存问题,特别是内存泄漏。幸运的是,现在有许多的技术和工具能够帮助我们验证内存泄漏是否存在,寻找到发生问题的位置。
内存泄漏一般指的是堆内存的泄漏。如果我们使用malloc函数或new操作符从堆中分配到一块内存,在使用完后,程序员必须负责调用相应的free或delete显式地释放该内存块,否则,这块内存就不能被再次使用,此时就出现了传说中的“内存泄漏”问题。如下面的代码片段所示:
- void Function(size_t nSize)
- {
- char* pChar= new char[nSize];
- if( !SetContent(pChar, nSize ) )
- {
- cout<<"Error: Fail To Set Content"<<endl;
- return;
- }
- ...//using pChar
- delete pChar;
- }
程序在入口处分配内存,在出口处释放内存,但是这里忽视了代码片段中的return;,如果函数SetContent()失败,指针pChar指向的内存就不会被释放,会发生内存泄漏。这是一种常见的内存泄漏情形。
检测内存泄漏的关键是要能截获对分配内存和释放内存的函数的调用。通过截获的这两个函数,我们就能跟踪每一块内存的生命周期。每当成功分配一块内存时,就把它的指针加入一个全局的内存链中;每当释放一块内存时,再把它的指针从内存链中删除。这样,当程序运行结束的时候,内存链中剩余的指针就会指向那些没有被释放的内存。这就是检测内存泄漏的基本原理。
检测内存泄漏的常用方法有如下几种:
MS C-Runtime Library内建的检测功能
使用MFC开发的应用程序时,会在Debug模式下编译执行,程序运行结束后,Visual C++(www.cppentry.com)会输出内存的使用情况,如果发生了内存泄漏,在Debug窗口中会输出所有发生泄漏的内存块的信息,如下所示:
- Detected memory leaks!
- Dumping objects ->
- mainFrm.cpp(45) : {352} normal block at 0x0058A4B8, 40 bytes long.
- Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
- Object dump complete.
这是因为在编译过程中,IDE自动加入了内存泄漏的检测代码。MFC在程序执行过程中维护了一个内存链,以便跟踪每一块内存的生命周期。在程序退出的时候,dbgheap.c文件中的extern "C" _CRTIMP int __cdecl _CrtDumpMemoryLeaks(void)函数被调用,遍历当前的内存链,如果发现存在没有被释放的内存,则打印出内存泄露的信息。
一般,大家都误以为这些内存泄漏的检测功能是由MFC提供的,其实不然。这是VC++(www.cppentry.com)的C运行库(CRT)提供的功能,MFC只是封装和利用了MS C-Runtime Library的Debug Function而已。所以,在编写非MFC程序时我们也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检测功能。
要在非MFC程序中打开内存泄漏的检测功能非常容易,只须在程序的入口处添加以下代码:
- _CrtSetDbgFlag( _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)
- | _CRTDBG_LEAK_CHECK_DF );
这样,在程序运行结束时,如果还有内存块没有释放,它们的信息就会被打印到Debug窗口里,如下面的代码片段所示:
- #include <crtdbg.h>
-
- #ifdef _DEBUG
- #define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
- #endif
- void EnableMemLeakCheck()
- {
- _CrtSetDbgFlag( _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)
- | _CRTDBG_LEAK_CHECK_DF );
- }
-
- int main()
- {
- EnableMemLeakCheck();
- _CrtSetBreakAlloc(53);
- int* pLeak = new int[10];
-
- return 0;
- }
在Debug模式下,程序退出时,内存块pLeak因为没有显式地释放,发生了内存泄漏,泄漏信息被打印出来: - Detected memory leaks!
- Dumping objects ->
- main.cpp(26) : {53} normal block at 0x002E1508, 40 bytes long.
- Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
- Object dump complete.
请读者思考一下,_CrtSetBreakAlloc(53)起到的是什么作用?
目前这种方式只支持MS系统开发环境。当然,如果开发系统环境是Linux,也可以根据MS C-Runtime Library内建检测功能的实现方式开发出自己的Linux C-Runtime Library内建检测版本。
外挂式的检测工具
如果开发的是一个大型程序,MS C-Runtime Library提供的检测功能便显得有点笨拙了。此时,我们可以采用外挂式的检测工具BoundsChecker或Insure++。