CRT のデバッグ技術
C ランタイム ライブラリを使用するプログラムをデバッグする場合、これらのデバッグ手法が役立つ場合があります。
CRT デバッグ ライブラリの使用方法
C ランタイム (CRT) ライブラリは、広範なデバッグサポートを提供します。 CRT デバッグ ライブラリのいずれかを使用するには、 /DEBUG
とリンクし、 /MDd
、 /MTd
、または /LDd
でコンパイルする必要があります。
CRT デバッグの主な定義とマクロは、<crtdbg.h>
ヘッダー ファイルにあります。
CRT デバッグ ライブラリの関数は、デバッグ情報 (/Z7、/Zd、/Zi、/ZI (デバッグ情報の形式)) を含んだ状態で、最適化されずにコンパイルされています。 渡されるパラメーターを検証するためのアサート ステートメントを含む関数もあり、これらの関数のソース コードは公開されています。 このソース コードを利用すると、CRT 関数をステップ実行して、その関数が正常に動作しているかを確認したり、パラメーターやメモリ状態が不正でないかどうかを検証したりできます。 (一部の CRT テクノロジは独自のものであり、例外処理、浮動小数点、およびその他のいくつかのルーチンのソース コードを提供していません)。
使用できる各種ランタイム ライブラリの詳細については、C ランタイム ライブラリに関するページを参照してください。
レポート用マクロの使用
デバッグでは、printf
ステートメントの使用を置き換えるために、_RPTn
マクロと _RPTFn
マクロ (<crtdbg.h>
で定義) を使用できます。 #ifdef
ディレクティブで囲む必要はありません。これは、_DEBUG
が定義されていないとリリース ビルドで自動的に消えるためです。
マクロ | 説明 |
---|---|
_RPT0 、 _RPT1 、 _RPT2 、 _RPT3 、 _RPT4 |
メッセージ文字列と、0 から 4 個の引数を出力します。 _RPT4 を使用した_RPT1 の場合、メッセージ文字列は引数の 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
と otherVar
の値を stdout
に出力します。 次のように _RPTF2
を呼び出すと、これらの値と一緒にファイル名と行番号も出力できます。
if (someVar > MAX_SOMEVAR) _RPTF2(_CRT_WARN, "In NameOfThisFunc( ), someVar= %d, otherVar= %d\n", someVar, otherVar );
一部のアプリケーションでは、C ランタイム ライブラリで提供されるマクロが提供しないデバッグ レポートが必要になる場合があります。 その場合は、独自の要件を満たす専用のマクロを記述できます。 たとえば、ヘッダー ファイルの 1 つに、次のようなコードを含めて、 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
を 1 回呼び出すと、printf
コードのすべての関数を実行できます。
ALERT_IF2(someVar > MAX_SOMEVAR, "OVERFLOW! In NameOfThisFunc( ),
someVar=%d, otherVar=%d.\n", someVar, otherVar );
カスタム マクロを簡単に変更して、さまざまな変換先に対してより多くの情報を報告することができます。 この方法は、デバッグ要件の進化に役立ちます。
デバッグ用フック関数の作成
デバッガーの通常の処理内の定義済みポイントにコードを挿入できる、いくつかの種類のカスタム デバッグ フック関数を記述できます。
Client ブロック用のフック関数
_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
を使用してインストールされた割り当てフック関数は、メモリが割り当て、再割り当て、または解放されるたびに呼び出されます。 この種類のフックは、さまざまな目的で使用できます。 メモリ不足の状況がアプリケーションでどのように処理されるかをテストする場合、たとえば、割り当てパターンを調査する場合や、後から分析するために割り当て情報をログに記録する場合に使用します。
Note
割り当てフック関数での 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
型の割り当て処理をレポートする必要がある場合は、この制限事項を回避するために、C ランタイム関数ではなく Windows API 関数を使用して書式設定や出力を行う方法があります。 Windows API は C ランタイム ライブラリのヒープを使用しないため、割り当て用のフック関数を使用しても無限ループに陥る心配はありません。
ランタイム ライブラリのソース ファイルを調べると、既定の割り当てフック関数 (TRUE
を返す) _CrtDefaultAllocHook
が、独自のdebug_heap_hook.cpp
の別のファイルに配置されていることがわかります。 アプリケーションの main
関数の前に実行されたランタイム スタートアップ コードによって行われた割り当てに対しても割り当てフックを呼び出す場合は、 _CrtSetAllocHook
を使用するのではなく、この既定の関数を独自の関数に置き換えることができます。
レポート用のフック関数
_CrtSetReportHook
を使用してインストールされたレポート フック関数は、デバッグ レポート_CrtDbgReport
生成されるたびに呼び出されます。 レポート用のフック関数を使用して、特定の割り当て型に関するレポートだけを出力できます。 レポート フック関数には、次の例のようなプロトタイプが必要です。
int AppReportHook(int nRptType, char *szMsg, int *retVal);
_CrtSetReportHook
に渡すポインターは、<crtdbg.h>
で定義されている_CRT_REPORT_HOOK
型です。
typedef int (__cdecl *_CRT_REPORT_HOOK)(int, char *, int *);
ランタイム ライブラリがフック関数を呼び出すとき、 nRptType
引数にはレポートのカテゴリ (_CRT_WARN
、 _CRT_ERROR
、または _CRT_ASSERT
) が含まれます。 szMsg
には、完全にアセンブルされたレポート メッセージ文字列へのポインターが含まれており、 retVal
_CrtDbgReport
レポートの生成後に通常の実行を続行するか、デバッガーを起動するかを指定します。 ( retVal
値 0 は実行を続行し、値 1 はデバッガーを起動します)。
フックが問題のメッセージを完全に処理し、それ以上の報告が不要な場合は、 TRUE
を返す必要があります。 FALSE
が返された場合、_CrtDbgReport
はメッセージを正常に報告します。
このセクションの内容
-
CRT が呼び出しをマップする方法、明示的に呼び出す利点、変換を回避する方法、クライアント ブロック内の個別の種類の割り当てを追跡する方法、
_DEBUG
を定義しなかった結果など、ヒープ割り当て関数の特別なデバッグ バージョンについて説明します。 -
メモリ管理とデバッグ ヒープ、デバッグ ヒープ上のブロックの種類、ヒープ状態レポート関数、およびデバッグ ヒープを使用して割り当て要求を追跡する方法について説明します。
-
デバッガーと C ランタイム ライブラリを使用してメモリ リークを検出および分離する手法について説明します。