CRT デバッグ ヒープ
ここでは、CRT デバッグ ヒープについて詳しく解説します。
このトピックの内容
メモリ管理とデバッグ ヒープ
デバッグ ヒープ ブロック型
デバッグ ヒープの機能
デバッグ ヒープの使用法
C++ デバッグの新機能および削除_CLIENT_BLOCKs コンパイルします。
ヒープの状態をレポートする関数
ヒープ割り当て要求の追跡
メモリ管理とデバッグ ヒープ
割り当てたバッファーの末尾を超えて書き込みをしてしまうこと、およびメモリ リーク (不要になったメモリ割り当てを解放し忘れること) は、プログラマが経験する問題の中で最も一般的でありながら、最もやっかいなものです。デバッグ ヒープは、このようなメモリ割り当ての問題を解決するための強力なツールとなります。
デバッグ バージョンのヒープ関数は、リリース ビルドで使用される標準バージョン (基本バージョン) の関数を呼び出します。メモリ ブロックの割り当てが要求されると、デバッグ ヒープ マネージャーは、要求されたサイズより少し大きめのメモリ ブロックをベース ヒープから割り当て、このブロック内のユーザー領域へのポインターを返します。たとえば、アプリケーション中で malloc( 10 ) という呼び出しをしたとします。リリース ビルドでは、malloc はベース ヒープ割り当てルーチンを呼び出し、10 バイトの割り当てを要求します。一方、デバッグ ビルドでは、malloc は _malloc_dbg を呼び出します。それはベース ヒープ割り当てルーチンを呼び出し、10 バイトに約 36 バイト加えたメモリを割り当てるよう要求します。デバッグ ヒープに割り当てられたすべてのメモリ ブロックは、単一のリンク リストとして、割り当てられた順番で連結されます。
デバッグ ヒープ ルーチンによって余分に割り当てられたメモリは、ブックキーピング情報や、デバッグ用のメモリ ブロックを連結するためのポインター用に使用されます。また、割り当てられた領域を超えて行われた書き込みを検出するために割り当て領域の前後に確保される小さなバッファーとしても使用されます。
現在、デバッグ ヒープ用のブックキーピング情報を格納するために使用されるブロック ヘッダー構造体は、DBGINT.H ヘッダー ファイルで次のように宣言されています。
typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
struct _CrtMemBlockHeader *pBlockHeaderPrev;
char *szFileName; // File name
int nLine; // Line number
size_t nDataSize; // Size of user block
int nBlockUse; // Type of block
long lRequest; // Allocation number
// Buffer just before (lower than) the user's memory:
unsigned char gap[nNoMansLandSize];
} _CrtMemBlockHeader;
/* In an actual memory block in the debug heap,
* this structure is followed by:
* unsigned char data[nDataSize];
* unsigned char anotherGap[nNoMansLandSize];
*/
ブロックのユーザー データの領域の前後に確保 NoMansLandのバッファーは、現在は 4 バイトで、ユーザーのメモリ ブロックの境界が上書きされていないことを確認するためにデバッグ ヒープ ルーチンに使用される既知のバイト値が格納されます。デバッグ ヒープは、新しく確保されたメモリ ブロックにも既知の値を格納します。解放されたブロックをヒープのリンク リストに保持するように設定した場合は、解放済みのブロックにも既知の値が格納されます。現在、実際に使用されているバイト値は次のとおりです。
NoMansLand (0xFD)
アプリケーションが使用するメモリ領域の前後に確保される "NoMansLand" バッファーには 0xFD が格納されます。解放済みのブロック (0xDD)
_CRTDBG_DELAY_FREE_MEM_DF フラグが設定されている場合、解放済みのブロックは使用されずにヒープのリンク リストに保持され、0xDD が格納されます。新規オブジェクト (0xCD)
新規オブジェクトには、メモリの割り当て時に 0xCD が格納されます。このトピックの内容
デバッグ ヒープ ブロック型
デバッグ ヒープ上のすべてのメモリ ブロックには、5 つの割り当て型のうちのいずれかの型が割り当てられます。型によって、メモリ リークの検出やメモリ状態をレポートするときの追跡方法やレポート方法が異なります。メモリ ブロックの型を指定するには、_malloc_dbg などのデバッグ ヒープ割り当て関数を直接呼び出してメモリ ブロックを割り当てます。デバッグ ヒープ上のメモリ ブロックの 5 つの型を次に示します。これらの型は、_CrtMemBlockHeader 構造体の nBlockUse メンバーに設定されます。
_NORMAL_BLOCK
malloc または calloc を呼び出すと Normal ブロックが作成されます。Client ブロックを使用せずに Normal ブロックだけを使用する場合は、_CRTDBG_MAP_ALLOC を定義することもできます。そうすると、デバッグ ビルドでは、すべてのヒープ割り当て関数の呼び出しが、対応するデバッグ バージョン関数の呼び出しに置き換えられます。これによって、ヒープ割り当て関数の呼び出しに関連するファイル名と行番号情報を対応するブロック ヘッダーに格納できます。_CRT_BLOCK
多数のランタイム ライブラリ関数は、内部的にメモリ ブロックを割り当てます。このように内部的に割り当てられたメモリ ブロックは CRT ブロックとして個別に扱われます。そのため、メモリ リークの検出などの処理には、これらのブロックは影響しません。CRT 型のブロックが、割り当て関数によって割り当てられたり、再割り当てされたり、解放されたりすることはありません。_CLIENT_BLOCK
アプリケーションでは、デバッグ ヒープ関数を明示的に呼び出し、このメモリ ブロック型を割り当てることによって、特定の割り当て領域をデバッグの目的で特別に追跡できます。たとえば、MFC はすべての CObjects を Client ブロックとして割り当てます。ほかのアプリケーションでも、さまざまなメモリ オブジェクトを Client ブロックとして保持している可能性があります。より細かい追跡を実施するために、Client ブロック型を細分化することもできます。Client ブロック型を細分化した型を指定するには、その型を表す数値を左に 16 ビットシフトし、_CLIENT_BLOCK との OR 演算を行います。次に例を示します。#define MYSUBTYPE 4 freedbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16));
Client ブロックに格納されているオブジェクトをダンプするためにクライアント側で定義したフック関数を _CrtSetDumpClient を使用して組み込むこともできます。これにより、デバッグ関数が Client ブロックの内容をダンプするたびに、このフック関数が呼び出されるようになります。また、_CrtDoForAllClientObjects を使用して、デバッグ ヒープ上の各 Client ブロックに対して、アプリケーション側で用意した特定の関数を呼び出すこともできます。
_FREE_BLOCK
通常は解放済みのブロックはリストから削除されます。ただし、解放済みのメモリに書き込みが行われていないかどうかをチェックしたり、メモリ不足の状態をシミュレートしたりするために、解放されたブロックをリンク リストに残しておくこともできます。その場合は、解放されていることを示すために、既知のバイト値 (現在は 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 フラグには、次のビット フィールドがあります。
ビット フィールド
既定値
value
説明
_CRTDBG_ALLOC_MEM_DF
On
デバッグ用の割り当てをオンにします。このビットがオフの場合は、割り当てブロックを連結されたままですが、そのブロック型は _IGNORE_BLOCK になります。
_CRTDBG_DELAY_FREE_MEM_DF
Off
メモリ不足の状態をシミュレートできるように、実際にはメモリを解放しません。このビットがオンの場合は、解放されたブロックはデバッグ ヒープのリンク リストに保持されます。このブロックは _FREE_BLOCK となり、特定のバイト値が格納されます。
_CRTDBG_CHECK_ALWAYS_DF
Off
の割り当ておよび解放のたびに呼び出される原因 _CrtCheckMemory。実行速度は遅くなりますが、エラーをすばやく検出できます。
_CRTDBG_CHECK_CRT_DF
Off
メモリ リークの検出やメモリ状態の比較の対象に含める _CRT_BLOCK 型としてマークされたブロックの原因。このビットがオフの場合、ランタイム ライブラリによって内部的に使用されるメモリは、これらの処理対象には含まれません。
_CRTDBG_LEAK_CHECK_DF
Off
プログラムの終了時に、_CrtDumpMemoryLeaks を呼び出してメモリ リークをチェックします。アプリケーションが割り当てたメモリを解放できていない場合は、エラー レポートが生成されます。
このトピックの内容
デバッグ ヒープの使用法
malloc、free、calloc、realloc、new、delete などのヒープ関数の呼び出しは、すべてデバッグ ヒープで動作するデバッグ バージョンのヒープ関数に置き換えられます。メモリ ブロックが解放されると、デバッグ ヒープは割り当て領域の前後に確保されたバッファーが完全かどうかを自動的にチェックし、それらのバッファーが上書きされていた場合はエラーを出力します。
デバッグ ヒープを使用するには
- アプリケーションのデバッグ ビルドに C ランタイム ライブラリのデバッグ バージョンをリンクします。
_crtDbgFlag の 1 つ以上のビット フィールドを変更してフラグの新しい状態を作成するには
newFlag パラメーターに _CRTDBG_REPORT_FLAG を設定して _CrtSetDbgFlag を呼び出すことによって、現在の _crtDbgFlag の状態を取得し、その値を一時変数に格納します。
この一時変数と、オンにするビットマスクとの OR (ビット演算子の |) 演算を行い、ビットをオンにします。ビットマスクは、アプリケーション コードの中ではマニフェスト定数で表されています。
一時変数と、オフにするビットマスクの NOT (ビット演算子の ~) との AND (ビット演算子の &) をとり、ビットをオフにします。
newFlag パラメーターに一時変数に格納されている値を設定して _CrtSetDbgFlag を呼び出すことによって、_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 );
C++ デバッグの新機能および削除_CLIENT_BLOCKs コンパイルします。
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 演算子は、すべてのブロック型に対して使用できるため、リリース バージョンのコンパイル時にプログラムを変更する必要はありません。
ヒープの状態をレポートする関数
_CrtMemState
ある時点でのヒープの状態のスナップショットを取得するには、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 レポート用の関数
ヒープの状態と内容をレポートするための関数を次に示します。これらの関数によって取得した情報を利用して、メモリ リークなどの問題を検出できます。
Function |
説明 |
---|---|
アプリケーションから提供された _CrtMemState 構造体のヒープのスナップショットを保存します。 |
|
メモリ状態を格納した 2 つの構造体を比較し、その相違点を別の構造体に保存します。2 つの状態が異なっている場合は TRUE を返します。 |
|
_CrtMemState の特定の構造をダンプします。この構造体には、ある時点でのデバッグ ヒープの状態のスナップショット、または 2 つのスナップショットの相違点が格納されています。 |
|
指定されたスナップショットの取得以降、またはプログラムの実行開始以降に割り当てられたすべてのオブジェクトに関する情報をダンプします。これは _CLIENT_BLOCK ブロックをダンプするたびに、1 は _CrtSetDumpClientを使用してインストール、フック関数をアプリケーション側で提供するフックします。 |
|
プログラムの実行開始以降にメモリ リークが発生したかどうかを調べます。メモリ リークが発生している場合は、割り当てられている全オブジェクトをダンプします。_CrtDumpMemoryLeaks が _CLIENT_BLOCK ブロックをダンプするたびに、1 は _CrtSetDumpClientを使用してインストール、フック関数をアプリケーション側で提供するフックします。 |
このトピックの内容
ヒープ割り当て要求の追跡
アサート マクロやレポート マクロの場合は、それらが実行されたソース ファイル名や行番号を特定することが、問題の原因となっている場所を突き止めるためにたいへん役に立ちますが、これはヒープ割り当て関数には当てはまりません。マクロはアプリケーションの論理ツリー内で適切な位置に挿入されますが、ヒープの割り当ては、さまざまな時点でさまざまな場所から呼び出される特殊なルーチンの中に埋もれていることが多いためです。通常は、不正なヒープ割り当てがコードのどの行で発生しているかではなく、その行で行われる膨大なヒープ割り当てのうち、どの割り当てが、どのような理由で不正になるかということが問題となります。
一意の割り当て要求番号と _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 が呼び出された場所のソース ファイル名と行番号が、デバッグ ヒープ上に割り当てられた各ブロックに格納されるため、このブロックを調べれば、これらの情報を出力できます。
このトピックの内容