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を出力しますotherVarstdout。 次のように _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しない結果など、ヒープ割り当て関数の特別なデバッグ バージョンについて説明します。

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

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

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

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

関連項目