CRT 偵錯技術

當您對使用 C 執行時間程式庫的程式進行偵錯時,這些偵錯技術可能會很有用。

CRT 偵錯程式庫操作

C 執行時間 (CRT) 程式庫提供廣泛的偵錯支援。 若要使用其中一個 CRT 偵錯程式庫,您必須使用 /DEBUG 連結並使用 、 /MTd/LDd 編譯 /MDd

CRT 偵錯的主要定義和宏可以在標頭檔中找到 <crtdbg.h>

CRT 偵錯程式庫裡的函式會在沒有最佳化情況下以偵錯資訊 (/Z7、/Zd、/Zi、/ZI (偵錯資訊格式)) 進行編譯。 有些函式包含可驗證傳入它們的參數之判斷提示,而且提供原始程式碼。 有了這個原始程式碼,您可以逐步執行 CRT 函式來確認函式是否如您希望的方式來執行,並檢查錯誤參數或記憶體狀態 (某些 CRT 技術是專屬的,不提供例外狀況處理、浮點和其他一些常式的原始程式碼。

如需各種可以使用的執行階段程式庫之詳細資訊,請參閱 C Run-Time Libraries (C 執行階段程式庫)。

報告巨集

若要進行偵錯,您可以使用 _RPTn 中定義的 和 _RPTFn 宏來取代 語句的使用 printf<crtdbg.h> 。 您不需要將它們括在 指示詞中 #ifdef ,因為它們會在未定義時 _DEBUG 自動消失在發行組建中。

Macro 描述
_RPT0, _RPT1, _RPT2, _RPT3, _RPT4 輸出訊息字串和零至四個引數。 對於 _RPT1_RPT4 ,訊息字串會做為引數的 printf 樣式格式字串。
_RPTF0, _RPTF1, _RPTF2, _RPTF3, _RPTF4 _RPTn 相同,但這些宏也會輸出宏所在的檔案名和行號。

請考慮下列範例:

#ifdef _DEBUG
    if ( someVar > MAX_SOMEVAR )
        printf( "OVERFLOW! In NameOfThisFunc( ),
               someVar=%d, otherVar=%d.\n",
               someVar, otherVar );
#endif

此程式碼會將 和 的值 someVar 輸出至 stdoutotherVar 您可以使用下列的 _RPTF2 呼叫來報告這些相同的值,此外,還有檔名和行號:

if (someVar > MAX_SOMEVAR) _RPTF2(_CRT_WARN, "In NameOfThisFunc( ), someVar= %d, otherVar= %d\n", someVar, otherVar );

某些應用程式可能需要偵錯報告 C 執行時間程式庫所提供的宏未提供。 在這些情況下,您可以撰寫專為符合您自己的需求而設計的宏。 例如,在其中一個標頭檔中,您可以包含類似下列的程式碼,以定義名為 ALERT_IF2 的宏:

#ifndef _DEBUG                  /* For RELEASE builds */
#define  ALERT_IF2(expr, msg, arg1, arg2)  do {} while (0)
#else                           /* For DEBUG builds   */
#define  ALERT_IF2(expr, msg, arg1, arg2) \
    do { \
        if ((expr) && \
            (1 == _CrtDbgReport(_CRT_ERROR, \
                __FILE__, __LINE__, msg, arg1, arg2))) \
            _CrtDbgBreak( ); \
    } while (0)
#endif

對 的呼叫 ALERT_IF2 可以執行程式碼的所有函式 printf

ALERT_IF2(someVar > MAX_SOMEVAR, "OVERFLOW! In NameOfThisFunc( ),
someVar=%d, otherVar=%d.\n", someVar, otherVar );

您可以輕鬆地將自訂宏變更為將更多或更少的資訊報告至不同的目的地。 隨著偵錯需求的發展,此方法很有用。

撰寫偵錯攔截函式

您可以撰寫數種自訂偵錯攔截函式,讓您將程式碼插入偵錯工具正常處理內的一些預先定義點。

用戶端區塊攔截函式

如果您要驗證或報告儲存在 _CLIENT_BLOCK 區塊裡的資料內容,您可以撰寫符合這個目的的函式。 您所撰寫的函式必須具有類似下列的原型,如 以下 <crtdbg.h> 所定義:

void YourClientDump(void *, size_t)

換句話說,您的攔截函式應該接受 void 配置區塊開頭的指標,以及 size_t 指出配置大小的型別值,並傳回 void 。 否則,其內容會由您決定。

一旦您使用 _CrtSetDumpClient 安裝攔截函式 ,就會在每次傾印區塊時 _CLIENT_BLOCK 呼叫它。 然後您可以使用 _CrtReportBlockType 來取得傾印區塊的類型或子類型資訊。

您傳遞至 _CrtSetDumpClient 之函式的指標類型為 _CRT_DUMP_CLIENT ,如以下 <crtdbg.h> 所述:

typedef void (__cdecl *_CRT_DUMP_CLIENT)
   (void *, size_t);

配置攔截函式

使用 _CrtSetAllocHook 安裝的配置攔截函式會在每次配置、重新配置或釋放記憶體時呼叫。 您可以針對許多不同的用途使用這種類型的勾點。 使用它來測試應用程式如何處理記憶體不足的情況,例如檢查配置模式,或記錄配置資訊以供稍後分析。

注意

請注意在配置攔截函式中使用 C 執行時間程式庫函式的限制,如配置攔截和 crt 記憶體配置 中所述

配置攔截函式應該具有類似下列範例的原型:

int YourAllocHook(int nAllocType, void *pvData,
        size_t nSize, int nBlockUse, long lRequest,
        const unsigned char * szFileName, int nLine )

您傳遞至 _CrtSetAllocHook 的指標類型為 _CRT_ALLOC_HOOK ,如以下 <crtdbg.h> 所述:

typedef int (__cdecl * _CRT_ALLOC_HOOK)
    (int, void *, size_t, int, long, const unsigned char *, int);

當執行時間程式庫呼叫您的攔截時,自 nAllocType 變數會指出即將進行的配置作業 ( _HOOK_ALLOC_HOOK_REALLOC_HOOK_FREE )。 在免費或重新配置中, pvData 有即將釋放之區塊之使用者文章的指標。 不過,針對配置,此指標為 null,因為配置尚未發生。 其餘引數包含配置大小、其區塊類型、循序要求編號,以及檔案名的指標。 如果有的話,引數也會包含配置所在的行號。 攔截函式執行其作者想要的任何分析和其他工作之後,必須傳回 TRUE 、表示配置作業可以繼續,或 FALSE ,表示作業應該失敗。 此類型的簡單勾點可能會檢查到目前為止配置的記憶體數量,如果該數量超過小限制,則傳回 FALSE 。 然後,應用程式會遇到通常只有在可用記憶體不足時才會發生的配置錯誤類型。 更複雜的攔截可能會追蹤配置模式、分析記憶體使用或在特定情況發生時報告。

配置攔截和 CRT 記憶體配置

配置攔截函式的重要限制是它們必須明確忽略 _CRT_BLOCK 區塊。 這些區塊是 C 執行時間程式庫函式在內部進行的記憶體配置,如果對配置內部記憶體的 C 執行時間程式庫函式進行任何呼叫。 您可以在配置攔截函式的開頭加入下列程式碼,以忽略 _CRT_BLOCK 區塊:

if ( nBlockUse == _CRT_BLOCK )
    return( TRUE );

如果您的配置攔截不會忽略 _CRT_BLOCK 區塊,則攔截中呼叫的任何 C 執行時間程式庫函式都可以在無休止的迴圈中攔截程式。 例如,printf 會執行內部配置。 如果您的攔截程式碼呼叫 printf ,則產生的配置會導致再次呼叫您的攔截,這會再次呼叫 printf ,依此等,直到堆疊溢位為止。 如果您要報告 _CRT_BLOCK 配置操作,避開這種限制的一種方法是使用 Windows API 函式,而不是執行階段函式來執行格式化和輸出。 因為 Windows API 不會使用 C 執行時間程式庫堆積,所以它們不會在無休止的迴圈中攔截您的配置攔截。

如果您檢查執行時間程式庫來源檔案,您會看到預設配置攔截函 _CrtDefaultAllocHook 式 (這只會傳回 TRUE ),位於自己的 debug_heap_hook.cpp 個別檔案中。 如果您想要即使針對在應用程式 main 函式之前執行的執行時間啟動程式碼所執行的配置呼叫配置攔截,您也可以將這個預設函式取代為您自己的函式,而不是使用 _CrtSetAllocHook

報告攔截函式

每次產生偵錯報表時 _CrtDbgReport ,都會呼叫使用 _CrtSetReportHook 安裝的報表攔截函式。 除此之外,您可以使用它來篩選報告以專注於特定類型的配置。 報表攔截函式應該具有類似此範例的原型:

int AppReportHook(int nRptType, char *szMsg, int *retVal);

您傳遞至 _CrtSetReportHook 的指標類型為 _CRT_REPORT_HOOK ,如 中所 <crtdbg.h> 定義:

typedef int (__cdecl *_CRT_REPORT_HOOK)(int, char *, int *);

當執行時間程式庫呼叫您的攔截函式時,自 nRptType 變數會包含報表類別目錄 ( _CRT_WARN_CRT_ERROR_CRT_ASSERT ), szMsg 包含完整組合報表訊息字串的指標,並 retVal 指定在產生報表或啟動偵錯工具之後,是否 _CrtDbgReport 應該繼續正常執行。 retVal(零的值會繼續執行,值為 1 會啟動偵錯工具。

如果攔截完全處理有問題的訊息,因此不需要進一步報告,它應該會傳回 TRUE 。 如果傳回 FALSE_CrtDbgReport 則會正常報告訊息。

本節內容

  • 堆積配置函式的偵錯版本

    討論堆積配置函式的特殊偵錯版本,包括:CRT 如何對應呼叫、明確呼叫它們的優點、如何避免轉換、追蹤用戶端區塊中的個別配置類型,以及未定義 _DEBUG 的結果。

  • CRT 偵錯堆積詳細資料

    描述記憶體管理和偵錯堆積、偵錯堆積上的區塊類型、堆積狀態報表函式,以及如何使用偵錯堆積來追蹤配置要求。

  • 使用 CRT 程式庫尋找記憶體流失

    說明使用偵錯工具和 C 執行階段程式庫來偵錯和找出記憶體流失問題的技術。

另請參閱