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
輸出至 stdout
。otherVar
您可以使用下列的 _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
的結果。 -
描述記憶體管理和偵錯堆積、偵錯堆積上的區塊類型、堆積狀態報告函式,以及如何使用偵錯堆積來追蹤配置要求。
-
說明使用偵錯工具和 C 執行階段程式庫來偵錯和找出記憶體流失問題的技術。