如何檢查內存泄露問題

簡單說明了一下沒有工具的情況如何運用VC庫中的工具來檢查代碼的內存泄漏問題。
一: 內存泄漏
        內存泄漏是編程中常常見到的一個問題,內存泄漏往往會一種奇怪的方式來表現出來,基本上每個程序都表現出不同的方式。 但是一般最後的結果只有兩個,一個是程序當掉,一個是系統內存不足。 還有一種就是比較介於中間的結果程序不會當,但是系統的反映時間明顯降低,需要定時的Reboot纔會正常。
        有 一個很簡單的辦法來檢查一個程序是否有內存泄漏。就是是用Windows的任務管理器(Task Manager)。運行程序,然後在任務管理器裏面查看 “內存使用”和”虛擬內存大小”兩項,當程序請求了它所需要的內存之後,如果虛擬內存還是持續的增長的話,就說明了這個程序有內存泄漏問題。 當然如果內存泄漏的數目非常的小,用這種方法可能要過很長時間才能看的出來。
        當然最簡單的辦法大概就是用CompuWare的BoundChecker 之類的工具來檢測了,不過這些工具的價格對於個人來講稍微有點奢侈了。
        如果是已經發布的程序,檢查是否有內存泄漏是又費時又費力。所以內存泄漏應該在Code的生成過程就要時刻進行檢查。
二: 原因
       內存泄漏產生的原因一般是三種情況:
分配完內存之後忘了回收;
程序Code有問題,造成沒有辦法回收;
某些API函數操作不正確,造成內存泄漏。
    1. 內存忘記回收,這個是不應該的事情。但是也是在代碼種很常見的問題。分配內存之後,用完之後,就一定要回收。如果不回收,那就造成了內存的泄漏,造成內存泄漏的Code如果被經常調用的話,那內存泄漏的數目就會越來越多的。從而影響整個系統的運行。比如下面的代碼:
for (int =0;I<100;I++)
{
    Temp = new BYTE[100];
}
就會產生 100*100Byte的內存泄漏。
    2. 在某些時候,因爲代碼上寫的有問題,會導致某些內存想回收都收不回來,比如下面的代碼:
Temp1 = new BYTE[100];
Temp2 = new BYTE[100];
Temp2 = Temp1;
這樣,Temp2的內存地址就丟掉了,而且永遠都找不回了,這個時候Temp2的內存空間想回收都沒有辦法。
    3. API函 數應用不當,在Windows提供API函數裏面有一些特殊的API,比如FormatMessage。 如果你給它參數中有FORMAT_MESSAGE_ALLOCATE_BUFFER,它會在函數內部New一塊內存Buffer出來。但是這個 buffer需要你調用LocalFree來釋放。 如果你忘了,那就會產生內存泄漏。
三: 檢查方法
        一 般的內存泄漏檢查的確是很困難,但是也不是完全沒有辦法。如果你用VC的庫來寫東西的話,那麼很幸運的是,你已經有了很多檢查內存泄漏的工具,只是你想不 想用的問題了。Visual C++的Debug版本的C運行庫(C Runtime Library)。它已經提供好些函數來幫助你診斷你的代碼和跟蹤內存泄漏。 而且最方便的地方是這些函數在Release版本中完全不起任何作用,這樣就不會影響你的Release版本程序的運行效率。
        比如下面的例子裏面,有一個明細的內存泄漏。當然如果只有這麼幾行代碼的話,是很容易看出有內存泄漏的。但是想在成千上萬行代碼裏面檢查內存泄漏問題就不是那麼容易了。
char * pstr = new char[5];
lstrcpy(pstr,"Memory leak");
        如 果我們在Debug版本的Code裏面對堆(Heap)進行了操作,包括malloc, free, calloc, realloc, new 和 delete可以利用VC Debug運行時庫中堆Debug函數來做堆的完整性和安全性檢查。比如上面的代碼,lstrcpy的操作明顯破壞了pstr的堆結構。使其溢出,並破壞 了臨近的數據。那我們可以在調用lstrcpy之後的代碼裏面加入 _CrtCheckMemory函數。_CrtCheckMemory函數發現前面的lstrcpy使得pstr的堆結構被破壞,會輸出這樣的報告:
emory check error at 0x00372FA5 = 0x79, should be 0xFD.
memory check error at 0x00372FA6 = 0x20, should be 0xFD.
memory check error at 0x00372FA7 = 0x6C, should be 0xFD.
memory check error at 0x00372FA8 = 0x65, should be 0xFD.
DAMAGE: after Normal block (#41) at 0x00372FA0.
Normal located at 0x00372FA0 is 5 bytes long.
       它 告訴說 pstr的長度應該時5個Bytes,但是在5Bytes後面的幾個Bytes也被非法改寫了。提醒你產生了越界操作。_CrtCheckMemory 的返回值只有TRUE和FALSE,那麼你可以用_ASSERTE()來報告出錯信息。 上面的語句可以換成 _ASSERTE(_CrtCheckMemory()); 這樣Debug版本的程序在運行的時候就會彈出一個警告對話框,這樣就不用在運行時候一直盯着Output窗口看了。這個時候按Retry,就可以進入源 代碼調試了。看看問題到底出在哪裏。
      其他類似的函數還有 _CrtDbgReport, _CrtDoForAllClientObjects, _CrtDumpMemoryLeaks,_CrtIsValidHeapPointer, _CrtIsMemoryBlock, _CrtIsValidPointer,_CrtMemCheckpoint, _CrtMemDifference, _CrtMemDumpAllObjectsSince, _CrtMemDumpStatistics, _CrtSetAllocHook, _CrtSetBreakAlloc, _CrtSetDbgFlag,_CrtSetDumpClient, _CrtSetReportFile, _CrtSetReportHook, _CrtSetReportMode
        這 些函數全部都可以用來在Debug版本中檢查內存的使用情況。具體怎麼使用這些函數就不在這裏說明了,各位可以去查查MSDN。在這些函數中用處比較大 的,或者說使用率會比較高的函數是_CrtMemCheckpoint, 設置一個內存檢查點。這個函數會取得當前內存的運行狀態。 _CrtMemDifference 檢查兩種內存狀態的異同。 _CrtMemDumpAllObjectsSince 從程序運行開始,或者從某個內存檢查點開始Dump出堆中對象的信息。還有就是_CrtDumpMemoryLeaks當發生內存溢出的時候Dump出堆 中的內存信息。 _CrtDumpMemoryLeaks一般都在有懷疑是內存泄漏的代碼後面調用。比如下面的例子:
#include <windows.h>
#include <crtdbg.h>
void main()
{
char * pstr;
pstr = new char[5];
_CrtDumpMemoryLeaks();
}
輸出:
Detected memory leaks! à提醒你,代碼有內存泄漏.
Dumping objects ->
{44} normal block at 0x00372DB8, 5 bytes long.
Data: < > CD CD CD CD CD
Object dump complete.
        如 果你雙擊包含行文件名的輸出行,指針將會跳到源文件中內存被分配地方的行。當無法確定那些代碼產生了內存泄漏的時候,我們就需要進行內存狀態比較。在可疑 的代碼段的前後設置內存檢查點,比較內存使用是否有可疑的變化。以確定內存是否有泄漏。爲此要先定義三個_CrtMemState 對象來保存要比較的內存狀態。兩個是用來比較,一個用了保存前面兩個之間的區別。
_CrtMemState Sh1,Sh2,Sh_Diff;
char *pstr1 = new char[100];
_CrtMemCheckPoint(&Sh1); ->設置第一個內存檢查點
char *pstr2 = new char[100];
_CrtMemCheckPoint(&Sh2); ->設置第二個內存檢查點
_CrtMemDifference(&Sh_Diff, &Sh1, &Sh2); ->檢查變化
_CrtMemDumpAllObjectsSince(&Sh_Diff); ->Dump變化
        如 果你的程序中使用了MFC類庫,那麼內存泄漏的檢查方法就相當的簡單了。因爲Debug版本的MFC本身就提供一部分的內存泄漏檢查。 大部分的new 和delete沒有配對使用而產生的內存泄漏,MFC都會產生報告。這個主要是因爲MFC重載了Debug版本的new 和delete操作符, 並且對前面提到的API函數重新進行了包裝。在MFC類庫中檢查內存泄漏的Class就叫 CMemoryState,它重新包裝了了_CrtMemState,_CrtMemCheckPoint, _CrtMemDifference, _CrtMemDumpAllObjectsSince這些函數。並對於其他的函數提供了Afx開頭的函數,供MFC程序使用。比如 AfxCheckMemory, AfxDumpMemoryLeaks 這些函數的基本用法同上面提到的差不多。 CMemoryState和相關的函數的定義都在Afx.h這個頭文件中。 有個簡單的辦法可以跟蹤到這些函數的聲明。在VC中找到MFC程序代碼中下面的代碼, 一般都在X.cpp的開頭部分
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
        把 光標移到DEBUG_NEW上面 按F12,就可以進入Afx.h中定義這些Class和函數的代碼部分。 VC中內存泄漏的常規檢查辦法主要是上面的兩種。當然這兩種方法只是針對於Debug版本的Heap的檢查。如果Release版本中還有內存泄漏,那麼 檢查起來就麻煩很多了。
4 .總結:
        實際上Heap的內存泄漏問題是相當的好查的。VC的提供的檢查工具也不太少,但是如果是棧出了什麼問題,恐怕就麻煩很多了。棧出問題,一般不會產生內存泄漏,但是你的代碼的邏輯上很有可能會有影響。這個是最最痛苦的事情。 編程,就是小心,小心再小心而已。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章