文丨樸艷麗 張 楠
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.