C++內存泄露和檢測

C++中的內存泄露通常指堆中的內存泄露。堆內存是咱們手動malloc/realloc/new申請的,程序不會自動回收,須要調用free或delete手動釋放,不然就會形成內存泄露。內存泄露其實還應該包括系統資料的泄露,好比socket鏈接等,使用完後也要釋放。ios

內存泄露的緣由:c++

總結下來,內存泄露大概有一下幾個緣由:web

一、編碼錯誤:malloc、realloc、new申請的內存在堆上,須要手動顯示釋放,調用free或delete。申請和釋放必須成對出現malloc/realloc對應free,new對應delete。前者不會運行構造/析構函數,後者會。對於C++內置數據類型可能沒差異,可是對於本身構造的類,可能在析構函數中釋放系統資源或釋放內存,因此要對應使用。緩存

二、「無主」內存:申請內存後,指針指向內存的起始地址,若丟失或修改這個指針,那麼申請的內存將丟失且沒有釋放。服務器

三、異常分支致使資源未釋放:程序正常執行沒有問題,可是若是遇到異常,正常執行的順序或分支會被打斷,得不到執行。因此在異常處理的代碼中,要確保系統資源的釋放。socket

四、隱式內存泄露:程序運行中不斷申請內存,可是直到程序結束才釋放。有些服務器會申請大量內存做爲緩存,或申請大量Socket資源做爲鏈接池,這些資源一直佔用直到程序退出。服務器運行起來通常持續幾個月,不及時釋放可能會致使內存耗盡。函數

五、類的析構函數爲非虛函數:析構函數爲虛函數,利用多態來調用指針指向對象的析構函數,而不是基類的析構函數。編碼


內存泄露的檢測spa

內存泄露的關鍵就是記錄分配的內存和釋放內存的操做,看看能不能匹配。跟蹤每一塊內存的聲明週期,例如:每當申請一塊內存後,把指向它的指針加入到List中,當釋放時,再把對應的指針從List中刪除,到程序最後檢查List就能夠知道有沒有內存泄露了。Window平臺下的Visual Studio調試器和C運行時(CRT)就是用這個原理來檢測內存泄露。指針

在VS中使用時,需加上

#define _CRTDBG_MAP_ALLOC

#include <crtdbg.h>

crtdbg.h的做用是將malloc和free函數映射到它們的調試版本_malloc_dbg和_free_dbg,這兩個函數將跟蹤內存分配和釋放(在Debug版本中有效)

_CrtDumpMemoryLeaks();

函數將顯示當前內存泄露,也就是說程序運行到此行代碼時的內存泄露,全部未銷燬的對象都會報出內存泄露,所以要讓這個函數儘可能放到最後。

例如:

  1. #define _CRTDBG_MAP_ALLOC  

  2. #include <crtdbg.h>  

  3. #include <iostream>  

  4. using namespace std;  

  5. int main(int argc,char** argv)  

  6. {  

  7.     char *str1 = NULL;  

  8.     char *str2 = NULL;  

  9.     str1=new char[100];  

  10.     str2=new char[50];  

  11.   

  12.     delete str1;  

  13.     _CrtDumpMemoryLeaks();  

  14.     return 0;  

  15. }  


上述代碼中,內存申請了兩塊,可是隻釋放了一塊,運行調試,會在output窗口輸出:

Dumping objects ->
{136} normal block at 0x00084D70, 50 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

能夠看到會檢測到內存泄露。 可是並無檢測到泄露內存申請的位置,已經加了宏定義#define _CRTDBG_MAP_ALLOC。緣由是申請內存用的是new,而剛剛包含頭文件和加宏定義是重載了malloc函數,並無重載new操做符,因此要本身定義重載new操做符才能檢測到泄露內存的申請位置。修改以下:

  1. #define _CRTDBG_MAP_ALLOC  

  2. #include <crtdbg.h>  

  3. #ifdef _DEBUG //重載new  

  4. #define new  new(_NORMAL_BLOCK, __FILE__, __LINE__)    

  5. #endif  

  6. #include <iostream>  

  7. using namespace std;  

  8. int main(int argc,char** argv)  

  9. {  

  10.     char *str1 = NULL;  

  11.     char *str2 = NULL;  

  12.     str1=(char*)malloc(100);  

  13.     str2=new char[50];  

  14.   

  15.     _CrtDumpMemoryLeaks();  

  16.     return 0;  

  17. }  

運行結果:

Detected memory leaks!
Dumping objects ->
e:\c++\test\內存泄露檢測2\main.cpp(13) : {62} normal block at 0x001714F8, 50 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
e:\c++\test\內存泄露檢測2\main.cpp(12) : {61} normal block at 0x00171458, 100 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

能夠看到

main.cpp()括號裏面的數字就是泄露內存的起始位置。那麼後面的{62} normal block at 0x001714F8, 50 bytes long.
表明什麼?

大括號{}裏面的數字表示第幾回申請內存操做;0x001714F8表示泄露內存的起始地址,CD CD表示泄露內存的內容。

爲何是第62次申請內存,由於在初始化操做時也申請了內存。經過這個信息,能夠設置斷點。調用long _CrtSetBreakAlloc(long nAllocID)能夠再第nAllocID次申請內存是中斷,在中斷時獲取的信息比在程序終止時獲取的信息要多,你能夠調試,查看變量狀態,對函數調用調試分析,解決內存泄露。

block分爲3中類型,此處爲normal,表示普通,此外還有client表示客戶端(專門用於MFC),CRT表示運行時(有CRT庫來管理,通常不會泄露),free表示已經釋放掉的塊,igore表示要忽略的塊。

在上面程序中,調用_CrtDumpMemoryLeaks()來檢測內存泄露,若是程序可能在多個地方終止,必須在多個地方調用這個函數,這樣比較麻煩,能夠在程序起始位置調用_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ),這樣不管程序什麼時候終止,都會在終止前調用_CrtDumpMemoryLeaks()。

 

除此以外,還能夠在某時刻設置檢查點,獲取當時內存狀態的快照。比較不一樣時刻內存狀態的差別。

  1. #define _CRTDBG_MAP_ALLOC  

  2. #include <crtdbg.h>  

  3. #ifdef _DEBUG //重載new  

  4. #define new  new(_NORMAL_BLOCK, __FILE__, __LINE__)    

  5. #endif  

  6. #include <iostream>  

  7. using namespace std;  

  8. int main(int argc,char** argv)  

  9. {  

  10.     _CrtMemState s1, s2, s3;  

  11.     char *str1 = NULL;  

  12.     char *str2 = NULL;  

  13.     str1=(char*)malloc(100);  

  14.     _CrtMemCheckpoint( &s1 );//記錄內存快照  

  15.     _CrtMemDumpStatistics( &s1 );//輸出  

  16.     str2=new char[50];  

  17.     _CrtMemCheckpoint( &s2 );  

  18.     _CrtMemDumpStatistics( &s2 );  

  19.   

  20.     if ( _CrtMemDifference( &s3, &s1, &s2) )//比較s1和s2,把比較結果輸出到s3  

  21.         _CrtMemDumpStatistics( &s3 );// dump 差別結果  

  22.   

  23.     return 0;  

  24. }  


輸出結果爲:

0 bytes in 0 Free Blocks.
100 bytes in 1 Normal Blocks.
8434 bytes in 54 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 8963 bytes.
Total allocations: 14003 bytes.
0 bytes in 0 Free Blocks.
150 bytes in 2 Normal Blocks.
8434 bytes in 54 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 8963 bytes.
Total allocations: 14053 bytes.
0 bytes in 0 Free Blocks.
50 bytes in 1 Normal Blocks.
0 bytes in 0 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 0 bytes.
Total allocations: 50 bytes.


也能夠用此法更復雜檢測內存泄露,例如設置檢查點,檢查檢查點之間的內存泄露。

在Linux下也有相似的方法,具體能夠參考:http://en.wikipedia.org/wiki/Mtrace

相關文章
相關標籤/搜索