C++記憶體配置與釋放均由使用者代碼自行控制,靈活的機制有如潘多拉之盒,即讓程式員有了更廣的發揮空間,也產生了代代相傳的記憶體流失問題。對於新手來說,
最常犯的錯誤就是new出一個對象而忘記釋放,對於一般小應用程式來說,一點記憶體空間不算什麼。但是當記憶體流失問題出現在需要24小時啟動並執行平台類程式上
的時候,將會使系統可用記憶體飛速減少,最後耗盡系統資源,導致系統崩潰。
所以學會如何防止並檢查記憶體流失,是一個合格的c++程式員
必須具備的能力。但是由於記憶體流失是程式運行並滿足一定條件時才會發生,直接從代碼中查出泄漏原因的難度較大,而且一旦記憶體流失發生在多線程程式中,從大
量的代碼中要靠人工找出泄漏原因,無論對新人還是老手都是一場噩夢。
本文介紹一種在vs2003中檢查記憶體流失的方法,供各位新人老手參考,在vc6中實現需要做一些變動,詳情可自行參照相關資料。
檢查策略分析
首先,假定我們需要檢測一個24小時啟動並執行平台程式的記憶體流失情況,我們無法確定具體的記憶體流失速度,但是我們可以確定該程式在一定時間內(如10分鐘)泄漏的記憶體量是接近的,設為L(eak)。
考慮在10分鐘的已耗用時間內程式新申請到的記憶體A(lloc),這部分記憶體其實包含了程式運行正常申請,並會在後續運行中進行釋放的普通記憶體塊N(ormal)和泄漏的記憶體L,即:
A = N + L
在後續的運行中,由於N部分不斷的申請和釋放,所以這部分的總量基本上是不變的,而L部分由於只申請而不釋放,佔用的記憶體總量將會越來越大。
將這個結果放到已耗用時間軸上,現在我們觀察程式運行中的20分鐘,我們假定記憶體流失速度為dL/10分鐘,時間軸如下:
----------------|--------------------|-------------------|----------------------------
Tn-2 Tn-1 Tn
三點間隔均為10分鐘,則我們有如下結論:
Tn點總的記憶體配置量 An = N + dL * n,N為正常分配記憶體,dL*n為記憶體流失量的總和,而Tn-1點的記憶體總量則為 An-1 =
N + dL*(n-1)。注意,我們這裡不考慮釋放的記憶體量,僅考慮增加的記憶體量。因此很明顯單位時間內的記憶體流失量 dL = An - An-1。
產生記憶體Dump檔案的代碼實現
要完成如上的策略,我們首先需要能跟蹤記憶體塊的分配與釋放情況,並且在運行時將分配情況儲存到檔案中,以便進行比較分析,所幸m$已經為我們提供了一整套手段,可以方便地進行記憶體追蹤。具體實現步驟如下:
包含記憶體追蹤所需庫
在StdAfx.h中添加如下代碼,注意必須定義宏_CRTDBG_MAP_ALLOC,否則後續dump檔案將缺少記憶體塊的代碼位置。
#ifdef _DEBUG //for memory leak check #define _CRTDBG_MAP_ALLOC //使產生的記憶體dump包含記憶體塊分配的具體代碼為止 #include<stdlib.h> #include<crtdbg.h> #endif |
啟動記憶體追蹤
上述步驟完成後,則可以在應用程式啟動處添加如下代碼,啟動記憶體追蹤,啟動後程式將自動檢測記憶體的分配與釋放情況,並允許將結果輸出。
//enable leak check _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG); |
將結果輸出指向dump檔案
由於預設情況下,記憶體流失的dump內容是輸出到vs的debug輸出視窗,但是對於服務類程式肯定沒法開著vs的debug模式來追蹤記憶體流失,所以必須將dump內容的輸出轉到dump檔案中。在程式中添加如下部分:
HANDLE hLogFile;//聲明記錄檔控制代碼 hLogFile = CreateFile("./log/memleak.log", GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);//建立記錄檔 _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);//將warn層級的內容都輸出到檔案(注意dump的報告層級即為warning) _CrtSetReportFile(_CRT_WARN, hLogFile);//將記錄檔設定為警示的輸出檔案 |
儲存記憶體Dump
完成了以上的設定,我們就可以在程式中添加如下代碼,輸出記憶體dump到指定的dump檔案中:
_CrtMemState s1, s2, s3;//定義3個臨時記憶體狀態 ...... _CrtDumpMemoryLeaks();//Dump從程式開始運行到該時刻點,已指派而未釋放的記憶體,即前述An //以下部分非必要,僅為方便後續分析增加資訊 _CrtMemCheckpoint( &s2 ); if ( _CrtMemDifference( &s3, &s1, &s2) ) { _CrtMemDumpStatistics( &s3 );//dump相鄰時間點間的記憶體塊變化 //for next compare _CrtMemCheckpoint( &s1 ); } time_t now = time(0); struct tm *nowTime = localtime(&now); _RPT4(_CRT_WARN,"%02d %02d:%02d:%02d snapshot dump./n", nowTime->tm_mday, nowTime->tm_hour,nowTime->tm_min,nowTime->tm_sec);//輸出該次dump時間 |
以上代碼最好放在一個函數中由定時器定期觸發,或者手動snapshot產生相等時間段的記憶體dump。
dump檔案內容樣本如下:
Detected memory leaks! Dumping objects -> {20575884} normal block at 0x05C4C490, 87 bytes long. Data: < > 02 00 1D 90 84 9F A6 89 00 00 00 00 00 00 00 00 ... d:/xxxxx/xxxworker.cpp(903) : {20575705} normal block at 0x05D3EF90, 256 bytes long. Data: < > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... Object dump complete. 0 bytes in 0 Free Blocks. 215968 bytes in 876 Normal Blocks. 0 bytes in 0 CRT Blocks. 0 bytes in 0 Ignore Blocks. 0 bytes in 0 Client Blocks. Largest number used: 220044 bytes. Total allocations: 7838322 bytes. 10 16:29:14 snapshot dump. |
上面紅色部分即為使用者代碼中分配而未釋放的記憶體塊位置。
解析Dump檔案
前面我們已經通過dump檔案擷取到各時刻點的記憶體dump,根據前面的分析策略,我們只需要將第n次dump的記憶體塊分配情況An,與第n-1次
dump記憶體塊分配情況An-1作比較,即可定位到發生記憶體流失的位置。由於dump檔案一般容量巨大,靠人工進行對比幾乎不可能,所以僅介紹比較的思
路,各位需要自行製作小工具進行處理。
1、提取兩個相鄰時間點的dump檔案D1和D2,設D1是D2之前的dump
2、各自提取dump檔案中使用者代碼分配的記憶體塊(即有明確代碼位置,而且為normal block的記憶體塊),分別根據記憶體塊ID(如“d:/xxxxx/xxxworker.cpp(903) : {20575705}”紅色部分)儲存在列表L1和L2
3、遍曆列表L2,記錄記憶體塊ID沒有在L1中出現過的記憶體塊,這些記憶體塊即為可能泄漏的記憶體
4、根據3的結果,按照記憶體的分配代碼位置,統計各處代碼泄漏的記憶體塊個數,降序排列,分配次數越多的代碼,記憶體流失可能性越大。