程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 菜鳥也能搞定C++內存洩漏

菜鳥也能搞定C++內存洩漏

編輯:關於C++

背景

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的結果,按照內存的分配代碼位置,統計各處代碼洩漏的內存塊個數,降序排列,分配次數越多的代碼,內存洩漏可能性越大。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved