オブジェクト ダンプの使用
オブジェクトの内容のダンプ
オブジェクトの内容の診断ダンプを取得することができます。CObject からクラスを派生させる場合は、Dump メンバ関数をオーバーライドして、そのオブジェクトのメンバ変数の内容をテキスト形式でダンプ コンテキストに書き込むことができます。ダンプ コンテキストは I/O ストリームと似ています。I/O ストリームと同様に、挿入演算子 (<<) を使ってデータを CDumpContext に送ることができます。
クラスを CObject から派生させる場合、必ずしも Dump をオーバーライドする必要はありません。ただし、デバッグ時にほかの診断機能を使うときは、オブジェクトをダンプしてその内容を確認した方がデバッグが簡単に行えるので、できる限り Dump をオーバーライドしてください。
オブジェクトをダンプする前に、デバッガでダンプ結果を参照できるように診断トレースを有効にしておく必要があります。
Dump メンバ関数をオーバーライドするには
- 基本クラスのメンバ関数 Dump を呼び出して、基本クラスのオブジェクトの内容をダンプします。
- 派生クラスの各メンバ変数について、説明のテキストと値を書き込みます。
次に、クラス宣言における Dump メンバ関数の宣言の例を示します。
class CPerson : public CObject
{
public:
#ifdef _DEBUG
virtual void Dump(CDumpContext& dc) const;
#endif
CString m_firstName;
CString m_lastName;
// etc. ...
};
オブジェクトのダンプは、プログラムのデバッグ中にだけ必要なので、Dump メンバ関数の宣言を #ifdef _DEBUG/#endif のブロックで囲みます。
次に、CPerson クラスのインプリメンテーション ファイルの例を示します。この例では、Dump 関数の最初のステートメントで基本クラスの Dump 関数を呼び出しています。次に各メンバ変数の短い説明を各メンバの値と一緒に診断ストリームに書き込んでいます。
#ifdef _DEBUG
void CPerson::Dump(CDumpContext& dc) const
{
// 最初に基本クラスの関数を呼び出す
CObject::Dump(dc);
//特定のクラスに値を入れる
dc << "last name: " << m_lastName << "\n"
<< "first name: " << m_firstName << "\n";
}
#endif
ここでも、Dump 関数の定義を #ifdef _DEBUG/#endif ディレクティブで囲みます。デバッグ バージョン以外のライブラリにリンクしたプログラム内で afxDump を参照すると、リンク時に解決できない外部参照エラーが発生します。
Dump の出力を afxDump に送るには
オブジェクトの Dump 関数を呼び出す場合、ダンプ結果の出力先を CDumpContext 引数で指定する必要があります。MFC では、オブジェクトの定型的なダンプ出力用に、定義済みの CDumpContext オブジェクトである afxDump を用意しています。次の例に、afxDump の使い方を示します。
CPerson* pMyPerson = new CPerson; // CPerson オブジェクトのフィールドを設定する... //... //内容をダンプする #ifdef _DEBUG pMyPerson->Dump(afxDump); #endif
Windows NT の場合、afxDump の出力はデバッガに送られます (デバッガが存在する場合)。それ以外の場合、afxDump の出力は得られません。
**メモ **afxDump はデバッグ バージョンの MFC でのみ定義されています。
詳細については、「TRACE マクロ」を参照してください。
すべてのオブジェクトのダンプ
プログラム中のすべてのオブジェクトの診断ダンプを取得できます。
C ランタイム オブジェクトのダンプについては、「デバッグ ヒープを使用する」および _CrtSetDbgFlag の説明を参照してください。
DumpAllObjectsSince を使うと、ヒープ領域から解放されずに残っているすべてのオブジェクトの説明をダンプできます。名前が示すように、DumpAllObjectsSince は直前に実行した Checkpoint 関数以降に割り当てられたすべてのオブジェクトをダンプします。Checkpoint が 1 度も実行されていないときは、メモリ上に存在するすべてのオブジェクトおよび非オブジェクトをダンプします。
MFC のオブジェクト ダンプを使用する場合は、その前に診断トレースを有効にする必要があります。
すべてのオブジェクトをダンプするには
「メモリ リークの検出」トピックに示す例に次のコードを追加した場合、メモリ リークが検出されると、解放されていないすべてのオブジェクトがダンプされます。
if(diffMemState.Difference(oldMemState, newMemState)) { TRACE("メモリ リークが発生しました!\n"); diffMemState.DumpAllObjectsSince(); }
次に、この例のダンプ出力を示します。
Dumping objects -> {5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long {4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long {3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long {2} a CPerson at $51A4 Last Name: Smith First Name: Alan Phone #: 581-0215 {1} strcore.cpp(80) : non-object block at $00A7516E, 25 bytes long
行の先頭の中かっこ内の数字は、そのオブジェクトが割り当てられた順序を示します。後に割り当てられたオブジェクトほど、上の行に表示されます。これらの番号は、割り当てられたオブジェクトの特定に役立ちます。
特定の割り当てに対してブレークポイントを設定するには、まずアプリケーションをデバッガから実行します。次に、グローバル変数 _afxBreakAlloc に、中かっこ内に示された数字を設定します。これで指定した割り当てが行われたときにトリガする条件付きブレークポイントがアプリケーションに設定されます。コール スタックを調べると、問題の割り当てが行われた経緯がわかります。
C ランタイム ライブラリにも同様の関数 _CrtSetBreakAlloc が用意されており、C ランタイムでの割り当てに使用することができます。
オブジェクト ダンプの解釈
上記のダンプは、「メモリ リークの検出」セクションに示したメモリ チェックポイントの例から取得したものです。このプログラムでは、メモリが明示的に割り当てられているのは 2 か所だけ (1 つはフレーム領域、もう 1 つはヒープ領域) です。
//メモリの割り当てと解放を行う
CString s = "This is a frame variable";
//次のオブジェクトはヒープ オブジェクト
CPerson* p = new CPerson("Smith", "Alan", "581-0215");
初めに CPerson オブジェクトについて説明します。このオブジェクトのコンストラクタは、引数として char への 3 つのポインタを取ります。コンストラクタは、これらの引数を使って CPerson クラスの CString のメンバ変数を初期化します。メモリ ダンプの内容を調べると、CPerson オブジェクトが 3 つの非オブジェクト ブロック (3、4、5) と共に割り当てられ、それぞれのブロックに CString のメンバ変数の文字が格納されていることがわかります。これらのメモリ ブロックは、CPerson オブジェクトのデストラクタが起動されたときに削除されます。
2 番のブロックは、CPerson オブジェクト自身を表します。CPerson のアドレス表示の後に、オブジェクトの内容が表示されています。これは、DumpAllObjectsSince が Dump メンバ関数を CPerson オブジェクトに対して呼び出した結果です。
1 番のブロックについては、関数呼び出しの順序とサイズから考えて、CString クラスのフレーム変数に割り当てられていることがわかります。ブロック サイズは、CString のフレーム変数の文字数と一致しています。フレーム変数による割り当ては、フレーム変数がスコープ外に出ると、自動的に解放されます。
通常、フレーム変数に割り当てられているヒープ オブジェクトについては、フレーム変数がスコープ外に出たときに自動的に解放されるので、メモリ リークについて心配する必要はありません。実際、メモリの診断ダンプが煩雑になるのを避けるためには、フレーム変数のスコープ外で Checkpoint を呼び出す必要があります。たとえば以下に示すように、前述のメモリ割り当てコードを中かっこで囲んでスコープを限定します。
oldMemState.Checkpoint();
{
//メモリの割り当てと解放を行う
CString s = "This is a frame variable";
//次のオブジェクトはヒープ オブジェクト
CPerson* p = new CPerson("Smith", "Alan", "581-0215");
}
newMemState.Checkpoint();
中かっこでスコープを限定したので、メモリ ダンプの出力は次のようになります。
Dumping objects ->
{5} strcore.cpp(80) : non-object block at $00A7521A, 9 bytes long
{4} strcore.cpp(80) : non-object block at $00A751F8, 5 bytes long
{3} strcore.cpp(80) : non-object block at $00A751D6, 6 bytes long
{2} a CPerson at $51A4
Last Name: Smith
First Name: Alan
Phone #: 581-0215
割り当てには、CPerson などのオブジェクトへの割り当てと、非オブジェクトへの割り当てがあります。非オブジェクトへの割り当てとは、CObject から派生していないオブジェクトへの割り当てと、char、int、long などの C の基本データ型への割り当てのことです。CObject の派生クラスで内部バッファなどの追加スペースを割り当てたときは、これらのオブジェクトはオブジェクトへの割り当てと非オブジェクトへの割り当ての両方を示します。
CString のフレーム変数に割り当てたメモリ ブロックは自動的に解放されたので、メモリ リークが発生していません。このように、スコープ規則によってメモリ領域を自動的に解放すると、フレーム変数に関連するメモリ リークの大部分を解消できます。
一方、ヒープ領域に割り当てたオブジェクトの場合、メモリ リークを防ぐためには明示的にオブジェクトを削除する必要があります。前の例の最後のメモリ リークをクリーン アップするには、次に示すように、ヒープ領域に割り当てられた CPerson オブジェクトを削除します。
{
//メモリの割り当てと解放を行う
CString s = "This is a frame variable";
//次のオブジェクトはヒープ オブジェクト
CPerson* p = new CPerson("Smith", "Alan", "581-0215");
delete p;
}
詳細については、「メモリ リークの検出」を参照してください。