CRT のデバッグ技術
C ランタイム ライブラリを使用するプログラムをデバッグする場合、これらのデバッグ手法が役立つ場合があります。
CRT デバッグ ライブラリの使用方法
C ランタイム (CRT) ライブラリは、広範なデバッグサポートを提供します。 CRT デバッグ ライブラリのいずれかを使用するには、/DEBUG
リンクして 、/MDd
/MTd
/LDd
または .
CRT デバッグ用のメイン定義とマクロは、ヘッダー ファイルにあります<crtdbg.h>
。
CRT デバッグ ライブラリの関数は、デバッグ情報 (/Z7、/Zd、/Zi、/ZI (デバッグ情報の形式)) を含んだ状態で、最適化されずにコンパイルされています。 渡されるパラメーターを検証するためのアサート ステートメントを含む関数もあり、これらの関数のソース コードは公開されています。 このソース コードを利用すると、CRT 関数をステップ実行して、その関数が正常に動作しているかを確認したり、パラメーターやメモリ状態が不正でないかどうかを検証したりできます。 (一部の CRT テクノロジは独自のものであり、例外処理、浮動小数点、およびその他のいくつかのルーチンのソース コードを提供していません)。
使用できる各種ランタイム ライブラリの詳細については、C ランタイム ライブラリに関するページを参照してください。
レポート用マクロの使用
デバッグでは、定義されているマクロと_RPTFn
マクロ<crtdbg.h>
を_RPTn
使用して、ステートメントのprintf
使用を置き換えることができます。 ディレクティブで #ifdef
囲む必要はありません。これは、定義されていないと _DEBUG
リリース ビルドで自動的に消えるためです。
マクロ | 説明 |
---|---|
_RPT0 , _RPT1 , _RPT2 , _RPT3 , _RPT4 |
メッセージ文字列と、0 から 4 個の引数を出力します。 for _RPT1 through _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
を出力します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
1 回の ALERT_IF2
呼び出しで、コードのすべての関数を 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)
言い換えると、フック関数は、割り当てブロックの先頭へのポインターとsize_t
、割り当てのサイズを示す型値を受け取void
り、返す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 です。 reメイン引数には、割り当てのサイズ、そのブロックの種類、順次要求番号、およびファイル名へのポインターが含まれます。 該当する場合、引数には、割り当てが行われた行番号も格納されます。 フック関数は、作成者が必要とする分析やその他のタスクを実行した後、割り当て操作を続行できることを示すか、操作FALSE
が失敗したことを示すタスクを返すTRUE
必要があります。 この型の単純なフックは、これまでに割り当てられたメモリの量をチェックし、その量が小さな制限を超えた場合に返される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 ランタイム ライブラリのヒープを使用しないため、割り当て用のフック関数を使用しても無限ループに陥る心配はありません。
ランタイム ライブラリのソース ファイルを調べると、既定の割り当てフック関数 _CrtDefaultAllocHook
(単に返 TRUE
されます) が、 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
引数にはレポートのカテゴリ (または szMsg
_CRT_ASSERT
) が_CRT_WARN
含まれており、_CRT_ERROR
完全にアセンブルされたレポート メッセージ文字列へのポインターが含まれており、レポートの生成後に通常の実行を続行するか、retVal
デバッガーを起動するかを_CrtDbgReport
指定します。 retVal
(値 0 は実行を続行し、値 1 を指定するとデバッガーが起動します)。
フックが問題のメッセージを完全に処理し、それ以上報告する必要がないようにする場合は、返す TRUE
必要があります。 返された FALSE
場合は、 _CrtDbgReport
メッセージが正常に報告されます。
このセクションの内容
-
CRT が呼び出しをマップする方法、明示的に呼び出す利点、変換を回避する方法、クライアント ブロック内の個別の種類の割り当てを追跡する方法、定義
_DEBUG
しない結果など、ヒープ割り当て関数の特別なデバッグ バージョンについて説明します。 -
メモリ管理とデバッグ ヒープ、デバッグ ヒープ上のブロックの種類、ヒープ状態レポート関数、およびデバッグ ヒープを使用して割り当て要求を追跡する方法について説明します。
-
デバッガーと C ランタイム ライブラリを使用してメモリ リークを検出および分離する手法について説明します。
関連項目
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示