文丨樸艷麗 張 楠
C++的強(qiáng)大功能之一就是可以進(jìn)行動(dòng)態(tài)內(nèi)存分配,即允許在程序運(yùn)行的過(guò)程中靈活分配所需的內(nèi)存,然而,有效地管理這些內(nèi)存同樣也是非常重要的。當(dāng)以前分配的一片內(nèi)存不再需要使用或無(wú)法訪問(wèn)時(shí),但是卻并沒(méi)有釋放它,那么對(duì)于該進(jìn)程來(lái)說(shuō),會(huì)因此導(dǎo)致總可用內(nèi)存的減少,這時(shí)就出現(xiàn)了內(nèi)存泄漏。
一般我們常說(shuō)的內(nèi)存泄露指的是堆內(nèi)存的泄露。應(yīng)用程序一般以malloc,new,relloc,等從內(nèi)存中分配的內(nèi)存空間,使用完成后程序必須負(fù)責(zé)free或者delete掉這部分內(nèi)存空間,否則這塊內(nèi)存就不能被再次使用就是我們通常說(shuō)的內(nèi)存泄露。此外,內(nèi)存泄露還包括系統(tǒng)資源的泄露。比如HANDLE,GDI Object,Socket,interface等,有些對(duì)象消耗的是核心態(tài)的內(nèi)存,因此,從某種角度上說(shuō),系統(tǒng)資源的泄露比堆內(nèi)存泄露更加恐怖,可能導(dǎo)致操作系統(tǒng)的不穩(wěn)定。GDI object泄露是一種常見(jiàn)的資源泄露。
以發(fā)生的方式來(lái)說(shuō),內(nèi)存泄露大致分為以下幾種:(1)常發(fā)性內(nèi)存泄露,發(fā)生內(nèi)存泄露的代碼會(huì)被經(jīng)常執(zhí)行到,每次執(zhí)行都會(huì)導(dǎo)致內(nèi)存泄露。(2)偶發(fā)性內(nèi)存泄露,發(fā)生內(nèi)存泄露的代碼只有在特定的環(huán)境下才會(huì)被執(zhí)行到,因此,在特定的環(huán)境下偶發(fā)內(nèi)存泄露可能會(huì)變成常發(fā)性內(nèi)存泄露,檢測(cè)內(nèi)存泄露變得至關(guān)重要。(3)一次性內(nèi)存泄露,發(fā)生內(nèi)存泄露的代碼只會(huì)被執(zhí)行一次。(4)隱式內(nèi)存泄露。程序在運(yùn)行過(guò)程中不停的分配內(nèi)存,但是直到結(jié)束后才釋放內(nèi)存,從嚴(yán)格的角度來(lái)說(shuō)這里并沒(méi)有內(nèi)存泄露,因?yàn)槌绦蜃罱K釋放了所有的申請(qǐng)資源,但是不及時(shí)的釋放內(nèi)存也有可能造成內(nèi)存泄露。
每當(dāng)分配一塊內(nèi)存,就把它的指針加入一個(gè)全局的list中,每當(dāng)釋放一塊內(nèi)存就把它的指針從全局list中取出,最后等到進(jìn)程關(guān)閉的時(shí)候,list中剩余的指針就是那些沒(méi)有釋放的內(nèi)存。
如果要檢測(cè)內(nèi)存泄露,那么只要截取住malloc/relloc/free和new/delete即可,其實(shí)(new/delete)也是malloc free 所以只要截獲前一組即可,對(duì)于其他的內(nèi)存泄露采用類似的方法,截獲住相應(yīng)的分配和釋放函數(shù)。比如檢測(cè)BSTR的泄露,就需要截獲SysallocString/SysfreeString,要檢測(cè)HMENU的泄露,就需要截獲CreateMenuDestoryMenu(有的資源分配函數(shù)由多個(gè)但是釋放函數(shù)只有一個(gè),此時(shí)需要截獲所有的分配函數(shù))。
要使除錯(cuò)函數(shù)生效,必須要在程序中包含以下幾個(gè)語(yǔ)句:
并且這些#include 語(yǔ)句必須按上邊給出的順序使用。如果你改變了順序,可能導(dǎo)致使用的函數(shù)工作不正常。包含crtdbg.h的作用是用malloc和free函數(shù)的debug版本(_malloc_dbg 和 _free_dbg)來(lái)替換他們,他們能跟蹤內(nèi)存分配和回收。這個(gè)替換僅僅是在debug狀態(tài)下生效,Relese版本中還是使用普通的malloc和free函數(shù)。上面的#define語(yǔ)句使用crt堆函數(shù)相應(yīng)的debug版本來(lái)替換正常的堆函數(shù)。這個(gè)語(yǔ)句不是必需的,但是沒(méi)有他,你可能會(huì)失去一些有用的內(nèi)存泄漏信息。
一旦在程序中增加了以上的語(yǔ)句,就可以通過(guò)在程序中增加_CrtDumpMemoryLeaks();函數(shù)來(lái)輸出內(nèi)存泄漏信息。當(dāng)你在debuger下運(yùn)行你的程序時(shí),_CrtDumpMemoryLeaks 顯示內(nèi)存泄漏信息在OutPut窗口的Debug標(biāo)簽項(xiàng)里。
一般有三種:(1)MS C-Runtime libray內(nèi)建的檢測(cè)功能;(2)外掛是檢測(cè)工具,諸如purify,boundschecker等;(3)利用windows NT自帶的performance Monitor。
BoundsChecker是一個(gè)運(yùn)行時(shí)錯(cuò)誤檢測(cè)工具,它主要定位程序運(yùn)行時(shí)期發(fā)生的各種錯(cuò)誤。它通過(guò)駐留在集成開發(fā)環(huán)境內(nèi)部的自動(dòng)處理調(diào)試程序來(lái)加速應(yīng)用程序的開發(fā),縮短產(chǎn)品發(fā)布時(shí)間。BoundsChecker對(duì)于編程中的錯(cuò)誤提供了清晰的詳細(xì)的分析。它能夠檢測(cè)和診斷出,在靜態(tài)堆棧內(nèi)存中的錯(cuò)誤以及內(nèi)存和資源泄漏問(wèn)題。在集成開發(fā)環(huán)境下,調(diào)試運(yùn)行DEBUG版程序,BoundsChecker在運(yùn)行時(shí)檢測(cè)內(nèi)存泄漏,并在可能出現(xiàn)內(nèi)存泄漏的代碼處中斷程序運(yùn)行,開發(fā)人員可根據(jù)調(diào)用現(xiàn)場(chǎng)狀態(tài),排除內(nèi)存泄漏。
調(diào)試運(yùn)行DEBUG版程序,運(yùn)用以下技術(shù):CRT(C run-time libraries)、運(yùn)行時(shí)函數(shù)調(diào)用堆棧、內(nèi)存泄漏時(shí)提示的內(nèi)存分配序號(hào)(集成開發(fā)環(huán)境OUTPUT窗口),綜合分析內(nèi)存泄漏的原因,排除內(nèi)存泄漏。
首先,需要在程序中包含必要的語(yǔ)句,用來(lái)啟用調(diào)試堆函數(shù)。其次,設(shè)置內(nèi)存泄漏檢測(cè)報(bào)告。在程序結(jié)束后,自動(dòng)調(diào)用_CrtDumpMemoryLeaks方法,在OUTPUT窗口中報(bào)告內(nèi)存泄漏的相關(guān)信息。最后,根據(jù)OUTPUT窗口中提示的內(nèi)存泄漏相關(guān)信息,排除泄漏。分兩種情況:一種情況是程序退出時(shí),在OUTPUT窗口中,直接報(bào)告出現(xiàn)內(nèi)存泄漏的源代碼文件名及具體代碼行數(shù)。只需要分析此處代碼,根據(jù)上、下文修改,一般就可以正確釋放內(nèi)存了;另外一種情況是使用_CrtSetBreakAlloc方法來(lái)檢查定位內(nèi)存泄漏位置。
具體操作步驟如下:(1)先在調(diào)試狀態(tài)下運(yùn)行幾次程序,觀察內(nèi)存分配順序號(hào)是哪幾個(gè)值。(2)用出現(xiàn)次數(shù)最多的那個(gè)順序號(hào)來(lái)設(shè)斷點(diǎn)。即:在代碼中添加如下調(diào)用:_CrtSetBreakAlloc(20);(假設(shè):OUTPUT窗口中,報(bào)告{20}最多。即:第20次內(nèi)存分配出現(xiàn)泄漏的情況較常發(fā)生)(3)在調(diào)試狀態(tài)下運(yùn)行程序,在斷點(diǎn)停下時(shí),打開"調(diào)用堆棧"窗口,找到對(duì)應(yīng)發(fā)生內(nèi)存泄漏的源代碼。(4)退出程序,觀察OUTPUT窗口的內(nèi)存泄漏報(bào)告,看本次內(nèi)存分配的順序號(hào)是不是和預(yù)設(shè)值(_CrtSetBreakAlloc中設(shè)置的值)相同,如果相同,就找到了;如果不同,就重復(fù)步驟(3),直到相同。(5)最后根據(jù)分析結(jié)果,在適當(dāng)?shù)奈恢冕尫欧峙涞膬?nèi)存。
由于自己在教學(xué)實(shí)踐及編程經(jīng)驗(yàn)上還有很多不足,上文中的錯(cuò)誤及不足之處希望大家多多批評(píng)、指正。
[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.