CRT デバッグ ヒープの詳細

CRT デバッグ ヒープと関連関数は、コード内のメモリ管理の問題を追跡およびデバッグするためのさまざまな方法を提供します。 これを使用して、バッファー オーバーランを見つけ、メモリ割り当てとメモリの状態を追跡して報告することができます。 また、独自のアプリのニーズに合わせて独自のデバッグ割り当て関数を作成することもできます。

デバッグ ヒープを使用してバッファー オーバーランを見つける

プログラマが遭遇する最も一般的で難しい問題の 2 つは、割り当てられたバッファーとメモリ リークの末尾を上書きすることです (不要になった後に割り当てを解放できない)。 デバッグ ヒープは、このようなメモリ割り当ての問題を解決するための強力なツールとなります。

デバッグ バージョンのヒープ関数は、リリース ビルドで使用される標準バージョン (基本バージョン) の関数を呼び出します。 メモリ ブロックを要求すると、デバッグ ヒープ マネージャーは、要求したよりも少し大きいメモリ ブロックをベース ヒープから割り当て、そのブロックの部分へのポインターを返します。 たとえば、アプリケーション中で malloc( 10 ) という呼び出しをしたとします。 リリース ビルドでは、 malloc 10 バイトの割り当てを要求するベース ヒープ割り当てルーチンを呼び出します。 ただし、デバッグ ビルドでは、 malloc 10 バイトと約 36 バイトの余分なメモリの割り当てを要求するベース ヒープ割り当てルーチンを呼び出 _malloc_dbgします。 デバッグ ヒープに割り当てられたすべてのメモリ ブロックは、単一のリンク リストとして、割り当てられた順番で連結されます。

デバッグ ヒープ ルーチンによって割り当てられる余分なメモリは、ブックキーピング情報に使用されます。 これには、デバッグ メモリ ブロックを一緒にリンクするポインターと、割り当てられたリージョンの上書きをキャッチするデータの両側の小さなバッファーがあります。

現在、デバッグ ヒープのブックキーピング情報を格納するために使用されるブロック ヘッダー構造は、ヘッダーで <crtdbg.h> 宣言され、CRT ソース ファイルで <debug_heap.cpp> 定義されています。 概念的には、次の構造に似ています。

typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
    _CrtMemBlockHeader* _block_header_next;
// Pointer to the block allocated just after this one:
    _CrtMemBlockHeader* _block_header_prev;
    char const*         _file_name;
    int                 _line_number;

    int                 _block_use;      // Type of block
    size_t              _data_size;      // Size of user block

    long                _request_number; // Allocation number
// Buffer just before (lower than) the user's memory:
    unsigned char       _gap[no_mans_land_size];

    // Followed by:
    // unsigned char    _data[_data_size];
    // unsigned char    _another_gap[no_mans_land_size];
} _CrtMemBlockHeader;

ブロックのユーザー データ領域の両側のバッファーのサイズは no_mans_land 現在 4 バイトであり、デバッグ ヒープ ルーチンによって使用される既知のバイト値でいっぱいになり、ユーザーのメモリ ブロックの制限が上書きされていないことを確認します。 デバッグ ヒープは、新しく確保されたメモリ ブロックにも既知の値を格納します。 解放されたブロックをヒープのリンク リストに保持することを選択した場合、これらの解放されたブロックにも既知の値が入力されます。 現在、実際に使用されているバイト値は次のとおりです。

no_mans_land (0xFD)
現在、アプリケーションによって使用されるメモリの両側にある "no_mans_land" バッファーには、0xFDが格納されています。

解放済みのブロック (0xDD)
_CRTDBG_DELAY_FREE_MEM_DF フラグが設定されている場合、解放済みのブロックは使用されずにヒープのリンク リストに保持され、0xDD が格納されます。

新規オブジェクト (0xCD)
新しいオブジェクトは、割り当てられると0xCDでいっぱいになります。

デバッグ ヒープ上のメモリ ブロックの型

デバッグ ヒープ上のすべてのメモリ ブロックには、5 つの割り当て型のうちのいずれかの型が割り当てられます。 型によって、メモリ リークの検出やメモリ状態をレポートするときの追跡方法やレポート方法が異なります。 次のような _malloc_dbgデバッグ ヒープ割り当て関数の 1 つを直接呼び出してブロックを割り当てることで、ブロックの型を指定できます。 デバッグ ヒープ内の 5 種類のメモリ ブロック (構造体の_CrtMemBlockHeaderメンバーにnBlockUse設定) は次のとおりです。

_NORMAL_BLOCK
標準ブロックの malloc 呼び出しまたは calloc 作成。 Normal ブロックのみを使用し、クライアント ブロックを必要としない場合は、定義 _CRTDBG_MAP_ALLOCすることができます。 _CRTDBG_MAP_ALLOC では、すべてのヒープ割り当て呼び出しがデバッグ ビルドで同等のデバッグにマップされます。 これにより、各割り当て呼び出しに関するファイル名と行番号情報を、対応するブロック ヘッダーに格納できます。

_CRT_BLOCK
多数のランタイム ライブラリ関数は、内部的にメモリ ブロックを割り当てます。このように内部的に割り当てられたメモリ ブロックは CRT ブロックとして個別に扱われます。 その結果、リーク検出やその他の操作はメイン影響を受けない可能性があります。 CRT 型のブロックが、割り当て関数によって割り当てられたり、再割り当てされたり、解放されたりすることはありません。

_CLIENT_BLOCK
アプリケーションでは、デバッグ ヒープ関数を明示的に呼び出し、このメモリ ブロック型を割り当てることによって、特定の割り当て領域をデバッグの目的で特別に追跡できます。 たとえば、MFC は、すべての CObject オブジェクトをクライアント ブロックとして割り当てます。他のアプリケーションでは、クライアント ブロックに異なるメモリ オブジェクトが保持される場合があります。 より細かい追跡を実施するために、Client ブロック型を細分化することもできます。 Client ブロック型を細分化した型を指定するには、その型を表す数値を左に 16 ビットシフトし、OR との _CLIENT_BLOCK 演算を行います。 次に例を示します。

#define MYSUBTYPE 4
freedbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16));

クライアント ブロックに格納されているオブジェクトをダンプするためのクライアント指定のフック関数を使用して _CrtSetDumpClientインストールできます。クライアント ブロックがデバッグ関数によってダンプされるたびに呼び出されます。 また、 _CrtDoForAllClientObjects デバッグ ヒープ内のすべてのクライアント ブロックに対して、アプリケーションによって提供される特定の関数を呼び出すためにも使用できます。

_FREE_BLOCK
通常は解放済みのブロックはリストから削除されます。 解放されたメモリが書き込まれていないことをチェックしたり、メモリ不足の状態をシミュレートしたりするには、リンク リストに解放されたブロックを保持し、Free としてマークし、既知のバイト値 (現在0xDD) を入力します。

_IGNORE_BLOCK
デバッグ ヒープ操作を一定の間隔でオフにすることができます。 この間は、メモリ ブロックはリスト中に保持されますが、Ignore ブロックとしてマークされます。

特定のブロックの型とサブタイプを確認するには、関数 _CrtReportBlockType とマクロ _BLOCK_TYPE を使用します _BLOCK_SUBTYPE。 マクロは次のように定義 <crtdbg.h> されます。

#define _BLOCK_TYPE(block)          (block & 0xFFFF)
#define _BLOCK_SUBTYPE(block)       (block >> 16 & 0xFFFF)

ヒープの整合性とメモリ リークを調べる

デバッグ ヒープ用の機能の多くは、コードの中からアクセスする必要があります。 次のセクションでは、これらの機能のいくつかについて、使用方法を説明します。

_CrtCheckMemory
たとえば、呼び出しを_CrtCheckMemory使用して、任意の時点でヒープの整合性をチェックできます。 この関数は、ヒープ内のすべてのメモリ ブロックを検査します。 メモリ ブロック ヘッダー情報が有効であることを確認し、バッファーが変更されていないことを確認します。

_CrtSetDbgFlag
デバッグ ヒープが内部フラグを使用して割り当てを追跡する方法を制御できます。このフラグは、 _crtDbgFlag関数を使用して _CrtSetDbgFlag 読み取りおよび設定できます。 このフラグを変更することによって、デバッグ ヒープを使用して、プログラムの終了時にメモリ リークをチェックし、検出されたメモリ リークをすべて出力できます。 同様に、メモリ不足の状況をシミュレートするために、解放されたメモリ ブロックをリンク リストに残すようにヒープに指示できます。 ヒープがチェックされると、これらの解放されたブロックが完全に検査され、それらが妨げられないことを確認します。

フラグには _crtDbgFlag 、次のビット フィールドが含まれています。

ビット フィールド 既定値 説明
_CRTDBG_ALLOC_MEM_DF オン デバッグ用の割り当てをオンにします。 このビットがオフの場合、割り当ては再メインチェーンされますが、ブロックの種類は _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF オフ​​ メモリ不足の状態をシミュレートできるように、実際にはメモリを解放しません。 このビットがオンの場合、解放されたブロックはデバッグ ヒープのリンク リストに保持されますが、マークされ、特殊なバイト値が入力されます _FREE_BLOCK
_CRTDBG_CHECK_ALWAYS_DF オフ​​ _CrtCheckMemory割り当てと割り当て解除のたびに呼び出されます。 実行は遅くなりますが、エラーをすばやくキャッチします。
_CRTDBG_CHECK_CRT_DF オフ​​ タイプ _CRT_BLOCK としてマークされたブロックをリーク検出および状態差操作に含めます。 このビットがオフの場合、ランタイム ライブラリによって内部的に使用されるメモリは、これらの処理対象には含まれません。
_CRTDBG_LEAK_CHECK_DF オフ​​ への呼び出しを介してプログラムの終了時に実行されるリークチェックを引き起こします_CrtDumpMemoryLeaks。 アプリケーションが割り当てたメモリを解放できていない場合は、エラー レポートが生成されます。

デバッグ ヒープを構成する

mallocfreecallocreallocnewdelete などのヒープ関数の呼び出しは、すべてデバッグ ヒープで動作するデバッグ バージョンのヒープ関数に置き換えられます。 メモリ ブロックが解放されると、デバッグ ヒープは割り当て領域の前後に確保されたバッファーが完全かどうかを自動的にチェックし、それらのバッファーが上書きされていた場合はエラーを出力します。

デバッグ ヒープを使用するには

  • アプリケーションのデバッグ ビルドを C ランタイム ライブラリのデバッグ バージョンにリンクします。

1 つ以上の _crtDbgFlag ビット フィールドを変更し、フラグの新しい状態を作成するには

  1. _CrtSetDbgFlag パラメーターに newFlag を設定して _CRTDBG_REPORT_FLAG を呼び出すことによって、現在の _crtDbgFlag の状態を取得し、その値を一時変数に格納します。

  2. 対応するビットマスク (マニフェスト定数によってアプリケーション コードで表される) を持つ一時変数に対してビットごとの | 演算子 ("または") を使用して、任意のビットを有効にします。

  3. 適切なビットマスクのビットごとの & 演算子 ("not" または補数) を持つ変数に対してビット演算子 ~ ("and") を使用して、他のビットをオフにします。

  4. _CrtSetDbgFlag パラメーターに一時変数に格納されている値を設定して newFlag を呼び出すことによって、_crtDbgFlag の新しい状態を作成します。

    たとえば、次のコード行は、自動リーク検出を有効にし、種類_CRT_BLOCKのブロックのチェックを無効にします。

    // Get current flag
    int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
    
    // Turn on leak-checking bit.
    tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
    
    // Turn off CRT block checking bit.
    tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;
    
    // Set flag to the new value.
    _CrtSetDbgFlag( tmpFlag );
    

newdeleteおよび _CLIENT_BLOCK C++ デバッグ ヒープ内の割り当て

C ランタイム ライブラリのデバッグ バージョンには、C++ の new 演算子と delete 演算子のデバッグ バージョンが含まれています。 _CLIENT_BLOCK 型を割り当てる場合は、次の例に示すように、new 演算子のデバッグ バージョンを直接呼び出すか、デバッグ モードで new 演算子を置き換えるマクロを作成する必要があります。

/* MyDbgNew.h
 Defines global operator new to allocate from
 client blocks
*/

#ifdef _DEBUG
   #define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
   #define DEBUG_CLIENTBLOCK
#endif // _DEBUG

/* MyApp.cpp
        Use a default workspace for a Console Application to
 *      build a Debug version of this code
*/

#include "crtdbg.h"
#include "mydbgnew.h"

#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif

int main( )   {
    char *p1;
    p1 =  new char[40];
    _CrtMemDumpAllObjectsSince( NULL );
}

デバッグ バージョンの delete 演算子は、すべてのブロック型に対して使用できるため、リリース バージョンのコンパイル時にプログラムを変更する必要はありません。

ヒープ状態レポート関数

特定の時点でのヒープの状態の概要スナップショットをキャプチャするには、次に定義<crtdbg.h>されている構造を_CrtMemState使用します。

typedef struct _CrtMemState
{
    // Pointer to the most recently allocated block:
    struct _CrtMemBlockHeader * pBlockHeader;
    // A counter for each of the 5 types of block:
    size_t lCounts[_MAX_BLOCKS];
    // Total bytes allocated in each block type:
    size_t lSizes[_MAX_BLOCKS];
    // The most bytes allocated at a time up to now:
    size_t lHighWaterCount;
    // The total bytes allocated at present:
    size_t lTotalCount;
} _CrtMemState;

この構造体には、デバッグ ヒープのリンク リストの先頭 (最近割り当てられた) ブロックへのポインターが格納されます。 次に、2 つの配列に、リストに含まれる各メモリ ブロックの種類 (_NORMAL_BLOCK_CLIENT_BLOCK_FREE_BLOCKなど) の数と、各種類のブロックに割り当てられたバイト数が記録されます。 最後に、その時点までにヒープに割り当てられたバイト数の最大値と、現在割り当てられているバイト数が格納されています。

その他の CRT レポート関数

ヒープの状態と内容をレポートするための関数を次に示します。これらの関数によって取得した情報を利用して、メモリ リークなどの問題を検出できます。

機能 説明
_CrtMemCheckpoint ヒープ_CrtMemStateのスナップショットを、アプリケーションによって提供される構造体に保存します。
_CrtMemDifference メモリ状態を格納した 2 つの構造体を比較し、その相違点を別の構造体に保存します。2 つの状態が異なっている場合は TRUE を返します。
_CrtMemDumpStatistics 特定 _CrtMemState の構造体をダンプします。 この構造体には、ある時点でのデバッグ ヒープの状態のスナップショット、または 2 つのスナップショットの相違点が格納されています。
_CrtMemDumpAllObjectsSince 指定されたスナップショットの取得以降、またはプログラムの実行開始以降に割り当てられたすべてのオブジェクトに関する情報をダンプします。 ブロックを _CLIENT_BLOCK ダンプするたびに、アプリケーションによって提供されるフック関数が呼び出されます (使用して _CrtSetDumpClientインストールされている場合)。
_CrtDumpMemoryLeaks プログラムの実行開始以降にメモリ リークが発生したかどうかを調べます。メモリ リークが発生している場合は、割り当てられている全オブジェクトをダンプします。 ブロックを_CLIENT_BLOCKダンプするたびに_CrtDumpMemoryLeaks、アプリケーションによって提供されるフック関数が呼び出されます (使用して_CrtSetDumpClientインストールされている場合)。

ヒープ割り当て要求を追跡する

アサート マクロまたはレポート マクロのソース ファイル名と行番号を知ることは、多くの場合、問題の原因を特定するのに役立ちます。 ヒープ割り当て関数に当てはまる可能性は高くありません。 アプリケーションのロジック ツリー内の多くの適切なポイントにマクロを挿入できますが、多くの場合、さまざまな場所から呼び出される関数に割り当てが埋め込まれます。 問題は、どのコード行が不適切な割り当てを行ったかではありません。 代わりに、そのコード行によって行われた何千もの割り当ての 1 つが悪かったのと、その理由です。

一意の割り当て要求番号と _crtBreakAlloc

問題が発生した特定のヒープ割り当て呼び出しを識別する簡単な方法があります。 デバッグ ヒープ内の各ブロックに関連付けられている一意の割り当て要求番号を利用します。 ダンプ関数を使用してブロックの情報を出力すると、この割り当て要求番号が中かっこで囲まれて表示されます ("{36}" など)。

不適切に割り当てられたブロックの割り当て要求番号がわかったら、この番号を渡して _CrtSetBreakAlloc ブレークポイントを作成できます。 このブロックが割り当てられる直前でプログラムの実行が停止するため、その時点からさかのぼって、不正な呼び出しの原因となっているルーチンを突き止めることができます。 再コンパイルを回避するには、関心のある割り当て要求番号を設定 _crtBreakAlloc することで、デバッガーで同じことを実行できます。

割り当てルーチンのデバッグ バージョンの作成

より複雑なアプローチは、ヒープ割り当て関数のバージョンに相当する _dbg 独自の割り当てルーチンのデバッグ バージョンを 作成することです。 その後、ソース ファイルと行番号の引数を基になるヒープ割り当てルーチンに渡すと、正しくない割り当てが発生した場所をすぐに確認できます。

たとえば、アプリケーションに、次の例のような一般的に使用されるルーチンが含まれているとします。

int addNewRecord(struct RecStruct * prevRecord,
                 int recType, int recAccess)
{
    // ...code omitted through actual allocation...
    if ((newRec = malloc(recSize)) == NULL)
    // ... rest of routine omitted too ...
}

ヘッダー ファイルには、次の例のようなコードを追加できます。

#ifdef _DEBUG
#define  addNewRecord(p, t, a) \
            addNewRecord(p, t, a, __FILE__, __LINE__)
#endif

次に、レコード作成ルーチンによる割り当てを次のように変更します。

int addNewRecord(struct RecStruct *prevRecord,
                int recType, int recAccess
#ifdef _DEBUG
               , const char *srcFile, int srcLine
#endif
    )
{
    /* ... code omitted through actual allocation ... */
    if ((newRec = _malloc_dbg(recSize, _NORMAL_BLOCK,
            srcFile, scrLine)) == NULL)
    /* ... rest of routine omitted too ... */
}

これで、addNewRecord が呼び出された場所のソース ファイル名と行番号が、デバッグ ヒープ上に割り当てられた各ブロックに格納されるため、このブロックを調べれば、これらの情報を出力できます。

関連項目

ネイティブ コードのデバッグ