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

このコードは、 someVarotherVar の値を 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を定義しなかった結果など、ヒープ割り当て関数の特別なデバッグ バージョンについて説明します。

  • CRT デバッグ ヒープの詳細

    メモリ管理とデバッグ ヒープ、デバッグ ヒープ上のブロックの種類、ヒープ状態レポート関数、およびデバッグ ヒープを使用して割り当て要求を追跡する方法について説明します。

  • CRT ライブラリを使用してメモリ リークを検出する

    デバッガーと C ランタイム ライブラリを使用してメモリ リークを検出および分離する手法について説明します。

関連項目