背景
C++內(nèi)存分配與釋放均由用戶代碼自行控制,靈活的機(jī)制有如潘多拉之盒,即讓程序員有了更廣的發(fā)揮
空間,也產(chǎn)生了代代相傳的內(nèi)存泄漏問題。對(duì)于新手來說,最常犯的錯(cuò)誤就是new出一個(gè)對(duì)象而忘記釋放,對(duì)于一般小應(yīng)用程序來說,一點(diǎn)內(nèi)存空間不算什么。但是當(dāng)內(nèi)存泄漏問題出現(xiàn)在需要24小時(shí)運(yùn)行的平臺(tái)類程序上的時(shí)候,將會(huì)使系統(tǒng)可用內(nèi)存飛速減少,最后耗盡系統(tǒng)資源,導(dǎo)致系統(tǒng)崩潰。
所以學(xué)會(huì)如何防止并檢查內(nèi)存泄漏,是一個(gè)合格的c++程序員必須具備的能力。但是由于內(nèi)存泄漏是程序運(yùn)行并滿足一定條件時(shí)才會(huì)發(fā)生,直接從代碼中查出泄漏原因的難度較大,而且一旦內(nèi)存泄漏發(fā)生在多線程程序中,從大量的代碼中要靠人工找出泄漏原因,無論對(duì)新人還是老手都是一場(chǎng)噩夢(mèng)
本文介紹一種在VS2003中檢查內(nèi)存泄漏的方法,供各位新人老手參考,在VC6中實(shí)現(xiàn)需要做一些變動(dòng),詳情可自行參照相關(guān)資料。
檢查策略分析
首先,假定我們需要檢測(cè)一個(gè)24小時(shí)運(yùn)行的平臺(tái)程序的內(nèi)存泄漏情況,我們無法確定具體的內(nèi)存泄漏速度,但是我們可以確定該程序在一定時(shí)間內(nèi)(如10分鐘)泄漏的內(nèi)存量是接近的,設(shè)為L(zhǎng)(eak)。
考慮在10分鐘的運(yùn)行時(shí)間內(nèi)程序新申請(qǐng)到的內(nèi)存A(lloc),這部分內(nèi)存其實(shí)包含了程序運(yùn)行正常申請(qǐng),并會(huì)在后續(xù)運(yùn)行中進(jìn)行釋放的普通內(nèi)存塊N(ormal)和泄漏的內(nèi)存L,即:A = N + L
在后續(xù)的運(yùn)行中,由于N部分不斷的申請(qǐng)和釋放,所以這部分的總量基本上是不變的,而L部分由于只申請(qǐng)而不釋放,占用的內(nèi)存總量將會(huì)越來越大。
將這個(gè)結(jié)果放到運(yùn)行時(shí)間軸上,現(xiàn)在我們觀察程序運(yùn)行中的20分鐘,我們假定內(nèi)存泄漏速度為dL/10分鐘,時(shí)間軸如下:
|
三點(diǎn)間隔均為10分鐘,則我們有如下結(jié)論:
Tn點(diǎn)總的內(nèi)存分配量 An = N + dL * n,N為正常分配內(nèi)存,dL*n為內(nèi)存泄漏量的總和,而Tn-1點(diǎn)的內(nèi)存總量則為 An-1 = N + dL*(n-1)。注意,我們這里不考慮釋放的內(nèi)存量,僅考慮增加的內(nèi)存量。因此很明顯單位時(shí)間內(nèi)的內(nèi)存泄漏量 dL = An - An-1。
生成內(nèi)存Dump文件的代碼實(shí)現(xiàn)
要完成如上的策略,我們首先需要能跟蹤內(nèi)存塊的分配與釋放情況,并且在運(yùn)行時(shí)將分配情況保存到文件中,以便進(jìn)行比較分析,所幸m$已經(jīng)為我們提供了一整套手段,可以方便地進(jìn)行內(nèi)存追蹤。具體實(shí)現(xiàn)步驟如下:
包含內(nèi)存追蹤所需庫(kù)
在StdAfx.h中添加如下代碼,注意必須定義宏_CRTDBG_MAP_ALLOC,否則后續(xù)dump文件將缺少內(nèi)存塊的代碼位置。
|
啟動(dòng)內(nèi)存追蹤
上述步驟完成后,則可以在應(yīng)用程序啟動(dòng)處添加如下代碼,啟動(dòng)內(nèi)存追蹤,啟動(dòng)后程序?qū)⒆詣?dòng)檢測(cè)內(nèi)存的分配與釋放情況,并允許將結(jié)果輸出。
|
將結(jié)果輸出指向dump文件
由于默認(rèn)情況下,內(nèi)存泄漏的dump內(nèi)容是輸出到vs的debug輸出窗口,但是對(duì)于服務(wù)類程序肯定沒法開著vs的debug模式來追蹤內(nèi)存泄漏,所以必須將dump內(nèi)容的輸出轉(zhuǎn)到dump文件中。在程序中添加如下部分:
|
保存內(nèi)存Dump
完成了以上的設(shè)置,我們就可以在程序中添加如下代碼,輸出內(nèi)存dump到指定的dump文件中:
|
以上代碼最好放在一個(gè)函數(shù)中由定時(shí)器定期觸發(fā),或者手動(dòng)snapshot生成相等時(shí)間段的內(nèi)存dump。
dump文件內(nèi)容示例如下:
|
上面紅色部分即為用戶代碼中分配而未釋放的內(nèi)存塊位置。
解析Dump文件
前面我們已經(jīng)通過dump文件獲取到各時(shí)刻點(diǎn)的內(nèi)存dump,根據(jù)前面的分析策略,我們只需要將第n次dump的內(nèi)存塊分配情況An,與第n-1次dump內(nèi)存塊分配情況An-1作比較,即可定位到發(fā)生內(nèi)存泄漏的位置。由于dump文件一般容量巨大,*人工進(jìn)行對(duì)比幾乎不可能,所以僅介紹比較的思路,各位需要自行制作小工具進(jìn)行處理。
1、提取兩個(gè)相鄰時(shí)間點(diǎn)的dump文件D1和D2,設(shè)D1是D2之前的dump
2、各自提取dump文件中用戶代碼分配的內(nèi)存塊(即有明確代碼位置,而且為normal block的內(nèi)存塊),分別根據(jù)內(nèi)存塊ID(如“d:\xxxxx\xxxworker.cpp(903) : {20575705}”紅色部分)保存在列表L1和L2
3、遍歷列表L2,記錄內(nèi)存塊ID沒有在L1中出現(xiàn)過的內(nèi)存塊,這些內(nèi)存塊即為可能泄漏的內(nèi)存
4、根據(jù)3的結(jié)果,按照內(nèi)存的分配代碼位置,統(tǒng)計(jì)各處代碼泄漏的內(nèi)存塊個(gè)數(shù),降序排列,分配次數(shù)越多的代碼,內(nèi)存泄漏可能性越大。


