VS環境中進行內存泄漏的檢測


根據MSDN中的介紹,親測整理。

本篇比較長,如不願花費太多時間,可只看第一段和第四段,甚至只看第四段。

內存泄漏,即未能正確釋放以前分配的內存,是 C/C++ 應用程序中最難以捉摸也最難以檢測到的 Bug 之一。借助 Visual Studio 調試器和 C 運行時 (CRT) 庫,可以檢測和識別內存泄漏。檢測內存泄漏的主要工具是調試器和 C 運行庫 (CRT) 調試堆函數。

簡單的使用

要調用CRT調試堆函數,需包含頭文件<crtdbg.h>。

在程序的退出點之前調用函數

_CrtDumpMemoryLeaks

();可簡單的顯示內存泄漏報告。
示例:
#include "stdlib.h"
#include <crtdbg.h>
int main()
{
    char *p =new char[30];
    char *p1=(char *)malloc(sizeof(char)*10);
    _CrtDumpMemoryLeaks();
    return 0;
}

輸出結果:

clip_image002

當程序有多個退出點時,在每一個退出點都調用_CrtDumpMemoryLeaks()顯然不是一個好主意,在程序的開頭部分調用函數_CrtSetDbgFlag會導致在每個退出點自動調用 _CrtDumpMemoryLeaks。如此調用_CrtSetDbgFlag必須設置兩個位域

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

解釋內存泄漏報告

{64}—— 表示是在第64次分配的內存,最終沒有被釋放掉,從而導致泄漏。CRT 報告包含運行 過程中的所有內存塊分配情況。其中包括 CRT 庫和其他庫(如 MFC)的分配情況。 所以不要疑惑這里為什么不是2.

Normal block—— 表示被泄露的內存塊為由程序分配的普通內存。另外,這個值還有可能是

Client Blocks ,“客戶端塊”是由 MFC 程序用於需要析構函數的對象的特殊 類型內存塊。 MFC new 運算符根據正在創建的對象的需要創建普通塊或客 戶端塊。

0x004C1DA0—— 表示發生泄漏的內存位置

10 bytes long.—— 遭泄露的內存塊的大小

Data: < > CD CD CD CD CD CD CD CD CD CD —— 遭泄露的塊中的數據,一般只顯示前16個字節

上述內存泄漏報告確實給出了不少信息,但很重要的一點卻沒有給出,那就是發生泄漏的代碼位置,畢竟我們需要據此來改善代碼。

獲取更詳細的內存泄漏報告

對於堆函數(例如 malloc、 free、 callocrealloc、 new 和 delete)都有其對應的調試版本,如果在檢測內存泄漏的過程中使用這些堆函數的調試版本,則可以輸出更詳細的內存泄漏報告。這並不難。

通過定義宏_CRTDBG_MAP_ALLOC可以使malloc,free等函數映射到它們的調試版本。

對於C++中的new和delete操作符,稍麻煩點,需要重新定義 new 才能在內存泄漏報告中看到文件和行號。

#ifdef _DEBUG

#ifndef DBG_NEW

#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )

#define new DBG_NEW

#endif

#endif // _DEBUG

示例:

#define _CRTDBG_MAP_ALLOC
#include "stdlib.h"
#include <crtdbg.h>

#ifdef _DEBUG
    #ifndef DBG_NEW
        #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
        #define new DBG_NEW
    #endif
#endif  // _DEBUG
int main()
{
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF |         \            _CRTDBG_LEAK_CHECK_DF );
    char *p =new char[30];
    char *p1=(char *)malloc(sizeof(char)*10);
//    _CrtDumpMemoryLeaks();
    return 0;
}

輸出結果:

clip_image005

至此,已經足夠滿足我們基本的需求了。

需要注意的一點是,宏_CRTDBG_MAP_ALLOC的定義必須放在include< crtdbg.h >之前,這表示在編譯crtdbg.h時,會根據這個宏來選擇編譯上述堆函數的Debug版本。


鎖定內存泄漏位置的另一個辦法是在內存分配編號上設置斷點。

1. 在應用程序的起點附近設置斷點,然后啟動應用程序。

2. 當應用程序在斷點處中斷時,會出現 “監視”窗口。

3. 在 “監視”窗口中,在 “名稱”列中鍵入 _crtBreakAlloc。

4. 如果要使用 CRT 庫的多線程 DLL 版本(/MD 選項),請加入上下文運算符: {,,msvcr100d.dll}_crtBreakAlloc(針對vs2010是msvcr100d.dll,其他版本的環境找對應的dll)

5. 在 “值”列中,將顯示的值替換為要在其位置中斷的內存分配的分配編號。

clip_image006

clip_image008

與其相似的另一個做法是在代碼中設置內存分配斷點。具體做法時在代碼的起始位置附近,調用如下函數(其實是個宏):

_CrtSetBreakAlloc(61);

參數61表示內存分配的分配編號,可由上述簡單的使用獲得。

這一下,我們獲得了更多詳細的報告——調用堆棧,可以更准確的鎖定發生內存泄漏的位置。但這種方法的不足之處是,對於在多線程代碼中,由於每次調試時的內存分配編號都會變化,就沒法使用這種辦法了。


定位內存泄漏的另一種技術涉及在關鍵點對應用程序的內存狀態拍快照。

若要為應用程序中給定點的內存狀態拍快照,請創建 _CrtMemState 結構,將它傳遞給 _CrtMemCheckpoint 函數。

該函數用當前內存狀態的快照填充此結構:

_CrtMemState s1;

_CrtMemCheckpoint( &s1 );

若要輸出 _CrtMemState 結構的內容,請將該結構傳遞給 _ CrtMemDumpStatistics 函數:

_CrtMemDumpStatistics( &s1 );

若要確定在某個代碼部分中是否發生了內存泄漏,可以對這部分之前和之后的內存狀態拍快照,然后使用 _CrtMemDifference 比較兩個狀態:

_CrtMemCheckpoint( &s1 );

// memory allocations take place here

_CrtMemCheckpoint( &s2 );

if ( _CrtMemDifference( &s3, &s1, &s2) )

_CrtMemDumpStatistics( &s3 );

示例代碼

#include "stdlib.h"
#include <crtdbg.h>
#include <Windows.h>
int main()
{
    _CrtMemState s1;
    _CrtMemState s4;
    _CrtMemCheckpoint( &s1 );
    OutputDebugString("第一次內存快照\n");
    _CrtMemDumpStatistics(&s1);
    char *p =new char[30];
    _CrtMemState s2;
    _CrtMemCheckpoint( &s2 );
    OutputDebugString("第二次內存快照\n");
    _CrtMemDumpStatistics(&s2);
    if ( _CrtMemDifference( &s4, &s1, &s2) )
    {    
        OutputDebugString("前兩次內存快照的差異\n");
        _CrtMemDumpStatistics( &s4 );
    }
    char *p1=(char *)malloc(sizeof(char)*10);
    _CrtMemState s3;
    _CrtMemCheckpoint( &s3 );
    OutputDebugString("第三次內存快照\n");
    _CrtMemDumpStatistics(&s3);
    if ( _CrtMemDifference( &s4, &s1, &s3) )
    {    
        OutputDebugString("首尾內存快照的差異\n");
        _CrtMemDumpStatistics( &s4 );
    }
    return 0;
}

clip_image011
普通塊是由程序分配的普通內存。

客戶端塊是由 MFC 程序用於需要析構函數的對象的特殊類型內存塊。

MFC new 運算符根據正在創建的對象的需要創建普通塊或客戶端塊。

“CRT 是由 CRT 庫為自己使用而分配的內存塊。

CRT 庫可處理這些塊的釋放。

因此,不大可能在內存泄漏報告中看到這些塊,除非出現嚴重錯誤(例如 CRT 庫損壞)。

內存泄漏報告中絕對不會出現另外兩個內存塊類型。

可用塊是已釋放的內存。

也就是說,根據定義,這種塊不會泄漏。

忽略塊是已明確標記、不出現在內存泄漏報告中的塊。

尋找內存泄漏的一個方法是,首先在應用程序的開頭和結尾部分放置 _CrtMemCheckpoint 調用,然后使用 _CrtMemDifference 比較兩個結果。

如果 _CrtMemDifference 顯示有內存泄漏,可以添加更多 _CrtMemCheckpoint 來進一步找到泄漏源。

這個方法看起來真夠笨拙的,不過它或許有別的好處吧。誰知道呢?


使用第三方工具

或許有人會嫌上述方法都太麻煩了,那不妨試試第三方工具,目前用於檢測內存泄漏的第三方工具可謂多種多樣,這里只推薦一款用與vs環境的第三方插件:Visual Leak Detector ,推薦理由:開源,免費。

下載地址:http://vld.codeplex.com/releases

目前已更新版本至v2.4rc2

使用方法及效果如下:

clip_image013

其調用堆棧的分析在控制台應用上並不十分准確,忽略了我們最關注的源代碼文件中調用位置。不過其在MFC中的表現還不賴。

以上源自 http://blog.csdn.net/yapingxin/article/details/6751940,話說這個blog里確實有很多好東西。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM