共用方式為


尋找 CRT 程式庫的記憶體流失問題

記憶體流失是 C/C++ 應用程式中最微妙且難以偵測的錯誤之一。 記憶體流失的原因是無法正確解除配置先前配置的記憶體。 一開始可能不會注意到小型記憶體流失,但隨著時間推移,可能會造成從效能不佳到應用程式記憶體不足時損毀等徵兆。 使用所有可用記憶體的流失應用程式可能會導致其他應用程式當機,造成對哪個應用程式負責的混淆。 即使是無害的記憶體流失也可能表示應該更正的其他問題。

Visual Studio 偵錯工具和 C 執行時間程式庫 (CRT) 可協助您偵測和識別記憶體流失。

啟用記憶體流失偵測

偵測記憶體流失的主要工具是 C/C++ 偵錯工具和 CRT 偵錯堆積函式。

若要啟用所有偵錯堆積函式,請依下列順序在 C++ 程式中加入下列語句:

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

#define 陳述式會將 CRT 堆積函式的基底版本對應到偵錯版本。 如果您省略 #define 語句,記憶體流失傾印將會 較不詳細

包括 crtdbg.h 會將 mallocfree 函式對應至其偵錯版本, _malloc_dbg 以及 _free_dbg ,其會追蹤記憶體配置和解除配置。 這種對應只會發生在偵錯組建 (具有 _DEBUG) 中。 發行的組建使用一般的 mallocfree 函式。

使用上述語句啟用偵錯堆積函式之後,請在應用程式結束點之前呼叫 _CrtDumpMemoryLeaks ,以在應用程式結束時顯示記憶體流失報告。

_CrtDumpMemoryLeaks();

如果您的 app 有數個結束,則不需要手動在每個結束點放置 _CrtDumpMemoryLeaks 。 若要在每一個結束點自動呼叫 _CrtDumpMemoryLeaks ,請在應用程式的開頭加上如下所示的位欄位來呼叫 _CrtSetDbgFlag

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

根據預設, _CrtDumpMemoryLeaks 會將記憶體流失報告輸出至 [輸出] 視窗的 [偵錯] 窗格。 如果您使用程式庫,該程式庫可能會重設輸出至其他位置。

您可以使用 _CrtSetReportMode 將報表重新導向至其他位置,或回到 [輸出 ] 視窗,如下所示:

_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG );

下列範例顯示簡單的記憶體流失,並使用 顯示記憶體流失資訊 _CrtDumpMemoryLeaks();

// debug_malloc.cpp
// compile by using: cl /EHsc /W4 /D_DEBUG /MDd debug_malloc.cpp
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>

int main()
{
    std::cout << "Hello World!\n";

    int* x = (int*)malloc(sizeof(int));

    *x = 7;

    printf("%d\n", *x);

    x = (int*)calloc(3, sizeof(int));
    x[0] = 7;
    x[1] = 77;
    x[2] = 777;

    printf("%d %d %d\n", x[0], x[1], x[2]);

    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); 
    _CrtDumpMemoryLeaks();
}

解譯記憶體流失報告

如果您的應用程式未定義 _CRTDBG_MAP_ALLOC _CrtDumpMemoryLeaks 會顯示如下所示的記憶體流失報告:

Detected memory leaks!
Dumping objects ->
{18} normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

如果您的應用程式定義 _CRTDBG_MAP_ALLOC ,記憶體流失報告看起來會像這樣:

Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\leaktest\leaktest.cpp(20) : {18}
normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

第二個報告會顯示第一次配置流失記憶體的檔案名和行號。

無論您是否定義 _CRTDBG_MAP_ALLOC ,記憶體流失報告會顯示:

  • 記憶體配置編號,在此範例中為 18
  • 範例中的區塊類型 normal
  • 範例中的十六進位記憶體位置 0x00780E80
  • 範例中的 區塊 64 bytes 大小。
  • 區塊中前 16 個位元組的資料 (十六進位格式)。

記憶體區塊類型為 一般 用戶端 CRT 「一般區塊」 (Normal Block) 是您的程式所配置的一般記憶體。 「用戶端區塊」 (Client Block) 是 MFC 程式專為需要解構函式的物件所使用的一種特殊類型的記憶體區塊。 MFC 的 new 運算子會根據所建立的物件來建立一般區塊或用戶端區塊。

「CRT 區塊」 (CRT Block) 則是 CRT 程式庫配置來供自身使用的記憶體區塊。 CRT 程式庫會處理這些區塊的解除配置,因此除非 CRT 程式庫發生嚴重問題,否則 CRT 區塊不會出現在記憶體流失報告中。

另外還有兩種絕對不會出現在記憶體流失報告中的記憶體區塊。 可用區塊 是已釋放的記憶體,因此依定義不會外泄。 忽略區塊 是您明確標示要從記憶體流失報告中排除的記憶體。

上述技術會識別使用標準 CRT malloc 函式配置之記憶體的記憶體流失。 不過,如果您的程式使用 C++ new 運算子配置記憶體,您可能只會看到記憶體流失報告中呼叫 _malloc_dbg 的檔案名和行號 operator new 。 若要建立更實用的記憶體流失報告,您可以撰寫類似下列的宏來報告進行配置的程式程式碼:

#ifdef _DEBUG
    #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
    // Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
    // allocations to be of _CLIENT_BLOCK type
#else
    #define DBG_NEW new
#endif

現在您可以使用程式碼中的 宏來取代 new 運算子 DBG_NEW 。 在偵錯組建中, DBG_NEW 會使用全域 operator new 多載,針對區塊類型、檔案和行號採用額外的參數。 要記錄額外資訊的呼叫 _malloc_dbg 多載 new 。 記憶體流失報告會顯示配置流失物件的檔案名和行號。 發行組建仍然使用預設 new 。 以下是技術的範例:

// debug_new.cpp
// compile by using: cl /EHsc /W4 /D_DEBUG /MDd debug_new.cpp
#define _CRTDBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>

#ifdef _DEBUG
    #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
    // Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
    // allocations to be of _CLIENT_BLOCK type
#else
    #define DBG_NEW new
#endif

struct Pod {
    int x;
};

void main() {
    Pod* pPod = DBG_NEW Pod;
    pPod = DBG_NEW Pod; // Oops, leaked the original pPod!
    delete pPod;

    _CrtDumpMemoryLeaks();
}

當您在 Visual Studio 偵錯工具中執行此程式碼時,呼叫 _CrtDumpMemoryLeaks 會在 [輸出 ] 視窗中產生類似如下的報表:

Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\debug_new\debug_new.cpp(20) : {75}
 normal block at 0x0098B8C8, 4 bytes long.
 Data: <    > CD CD CD CD
Object dump complete.

此輸出報告洩漏的配置位於 debug_new.cpp 的第 20 行。

注意

不建議您建立名為 new 、 或任何其他語言關鍵字的預處理器宏。

在記憶體配置編號上設定中斷點

記憶體配置編號會指出流失的記憶體區塊是何時配置的。 例如,記憶體配置編號為 18 的區塊是應用程式執行期間配置的第 18 個記憶體區塊。 CRT 報表會計算執行期間的所有記憶體區塊配置,包括 CRT 程式庫和其他程式庫的配置,例如 MFC。 因此,記憶體配置區塊編號 18 可能不是程式碼所配置的第 18 個記憶體區塊。

您可以使用配置編號在記憶體配置上設定中斷點。

若要使用 [監看式] 視窗設定記憶體配置中斷點

  1. 在應用程式的開頭附近設定中斷點,並開始偵錯。

  2. 當應用程式在中斷點暫停時,選取 > [偵錯 Windows > 監看式 1 ] 來開啟 [ 監看 式] 視窗(或 [監看式 2 ]、 [監看式 3 ] 或 [監看式 4]。

  3. 在 [ 監看 式] 視窗中,輸入 _crtBreakAlloc [ 名稱] 資料行。

    如果您使用 CRT 程式庫的多執行緒 DLL 版本 (/MD 選項),請新增內容運算子: {,,ucrtbased.dll}_crtBreakAlloc

    請確定已載入偵錯符號。 否則, _crtBreakAlloc 會回報為 不明

  4. Enter

    偵錯工具會評估呼叫,並將結果放在 [值] 欄中。 如果您尚未在記憶體配置上設定任何中斷點,此值為 -1

  5. 在 [ 值] 資料行中,將值取代為您想要偵錯工具中斷的記憶體配置配置編號。

在記憶體配置編號上設定中斷點之後,請繼續偵錯。 請務必在相同的條件下執行,因此記憶體配置號碼不會變更。 當您的程式在指定的記憶體配置中斷時,請使用 [呼叫堆疊 ] 視窗和其他偵錯工具視窗來判斷記憶體配置的條件。 然後,您可以繼續執行來觀察物件會發生什麼情況,並判斷其未正確解除配置的原因。

在物件上設定資料中斷點可能也很有用。 如需詳細資訊,請參閱使用中斷點

您也可以在程式碼中設定記憶體配置中斷點。 您可以設定:

_crtBreakAlloc = 18;

or:

_CrtSetBreakAlloc(18);

比較記憶體狀態

另一種尋找記憶體流失的技術,是使用在關鍵點的應用程式記憶體狀態之快照。 若要在應用程式中指定點建立記憶體狀態的快照集,請建立 _CrtMemState 結構,並將它傳遞至 函 _CrtMemCheckpoint 式。

_CrtMemState s1;
_CrtMemCheckpoint( &s1 );

_CrtMemCheckpoint 式會以目前記憶體狀態的快照填入 結構。

若要輸出結構的內容 _CrtMemState ,請將 結構傳遞至 函 _ CrtMemDumpStatistics 式:

_CrtMemDumpStatistics( &s1 );

_CrtMemDumpStatistics 輸出記憶體狀態傾印,如下所示:

0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
3071 bytes in 16 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 3071 bytes.
Total allocations: 3764 bytes.

若要判斷記憶體流失是否發生在程式碼區段裡,您可以建立區段前和區段後的記憶體狀態快照,然後使用 _CrtMemDifference 比較這兩個狀態:

_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );

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

_CrtMemDifference 會比較記憶體狀態 s1 和 ,並以 s2s3 ) 傳回 與 之間的差異 s1s2 結果。

尋找記憶體流失的其中一個技巧,一開始是在應用程式的開頭和結尾放置 _CrtMemCheckpoint 呼叫,然後使用 _CrtMemDifference 來比較結果。 如果 _CrtMemDifference 顯示記憶體流失,您可以新增更多 _CrtMemCheckpoint 呼叫,以使用二進位搜尋來分割程式,直到您隔離洩漏的來源為止。

誤報

_CrtDumpMemoryLeaks 如果程式庫將內部配置標示為一般區塊,而不是 CRT 區塊或用戶端區塊,可能會提供記憶體流失的錯誤指示。 在這種情況下, _CrtDumpMemoryLeaks 無法區分使用者配置和內部程式庫配置。 如果在您呼叫 _CrtDumpMemoryLeaks之後執行程式庫配置的全域解構函式,則每個內部程式庫配置都會報告為記憶體流失。 比 Visual Studio .NET 更早的標準範本程式庫版本可能會導致 _CrtDumpMemoryLeaks 回報這類誤判。

另請參閱