次の方法で共有


MFC デバッグ手法

MFC プログラムをデバッグする場合は、これらのデバッグ手法が役立つ場合があります。

AfxDebugBreak

MFC には、ソース コード内のブレークポイントをハードコーディングするための特別な AfxDebugBreak 関数が用意されています。

AfxDebugBreak( );

Intel プラットフォームでは、 AfxDebugBreak は次のコードを生成しますが、これはカーネル・コードではなくソース・コードで中断されます。

_asm int 3

他のプラットフォームでは、 AfxDebugBreak は単に DebugBreak を呼び出すだけです。

リリース ビルドを作成するとき、または#ifdef _DEBUGを使用してステートメントを囲むときは、AfxDebugBreak ステートメントを必ず削除してください。

TRACE マクロ

デバッガーの [出力] ウィンドウにプログラムからのメッセージを表示するには、 ATLTRACE マクロまたは MFC TRACE マクロを使用できます。 アサーションと同様に、トレース マクロはプログラムのデバッグ バージョンでのみアクティブになり、リリース バージョンでコンパイルすると表示されなくなります。

以下の例は、 TRACE マクロの使用方法の一部を示しています。 printf と同様に、TRACE マクロはいくつかの引数を処理できます。

int x = 1;
int y = 16;
float z = 32.0;
TRACE( "This is a TRACE statement\n" );

TRACE( "The value of x is %d\n", x );

TRACE( "x = %d and y = %d\n", x, y );

TRACE( "x = %d and y = %x and z = %f\n", x, y, z );

TRACE マクロは、char* パラメータと wchar_t* パラメータの両方を適切に処理します。 次の例は、TRACE マクロをさまざまなタイプの文字列パラメータと共に使用する方法を示しています。

TRACE( "This is a test of the TRACE macro that uses an ANSI string: %s %d\n", "The number is:", 2);

TRACE( L"This is a test of the TRACE macro that uses a UNICODE string: %s %d\n", L"The number is:", 2);

TRACE( _T("This is a test of the TRACE macro that uses a TCHAR string: %s %d\n"), _T("The number is:"), 2);

TRACE マクロの詳細については、診断サービスを参照してください。

MFC でのメモリ リークの検出

MFC には、割り当てられているが割り当てが解除されていないメモリを検出するためのクラスと関数が用意されています。

メモリ割り当ての追跡

MFC では、新しい演算子の代わりにマクロ DEBUG_NEW を使用して、メモリ リークを特定できます。 プログラムのデバッグ・バージョンでは、 DEBUG_NEW は、割り振る各オブジェクトのファイル名と行番号を追跡します。 プログラムのリリース バージョンをコンパイルすると、 DEBUG_NEW ファイル名と行番号の情報を含まない単純な 新しい 操作に解決されます。 したがって、プログラムのリリースバージョンでは速度ペナルティを支払うことはありません。

new の代わりに DEBUG_NEW を使用するようにプログラム全体を書き換えたくない場合は、ソース ファイルで次のマクロを定義できます。

#define new DEBUG_NEW

オブジェクトダンプを実行すると、DEBUG_NEWが割り当てられた各オブジェクトには、割り当てられたファイルと行番号が表示されるため、メモリリークの原因を特定できます。

MFC フレームワークのデバッグ バージョンでは DEBUG_NEW が自動的に使用されますが、コードでは使用されません。 DEBUG_NEW の利点が必要な場合は、DEBUG_NEW を明示的に使用するか、上記のように新しい #define を使用する必要があります。

メモリ診断の有効化

メモリ診断機能を使用する前に、診断トレーシングを有効にする必要があります。

メモリ診断を有効または無効にするには

  • グローバル関数 AfxEnableMemoryTracking を呼び出して、診断メモリ アロケーターを有効または無効にします。 デバッグ ライブラリではメモリ診断が既定でオンになっているため、通常はこの関数を使用して一時的にオフにすると、プログラムの実行速度が向上し、診断出力が低下します。

    afxMemDF で特定のメモリ診断機能を選択するには

  • メモリ診断機能をより正確に制御する場合は、MFC グローバル変数 afxMemDF の値を設定することで、個々のメモリ診断機能のオンとオフを選択的に切り替えることができます。 この変数は、列挙型 afxMemDF で指定される次の値を持つことができます。

    価値 説明
    allocMemDFの 診断メモリ アロケータをオンにします (デフォルト)。
    遅延フリーメモリDF delete または free を呼び出すときにメモリの解放を、プログラムが終了するまで遅延させます。 これにより、プログラムは可能な限り最大量のメモリを割り当てることになります。
    checkAlwaysMemDF メモリが割り当てられるか解放されるたびに AfxCheckMemory を呼び出します。

    これらの値は、次に示すように論理和演算を実行することで組み合わせて使用できます。

    afxMemDF = allocMemDF | delayFreeMemDF | checkAlwaysMemDF;
    

メモリスナップショットの取得

  1. CMemoryState オブジェクトを作成し、CMemoryState::Checkpoint メンバー関数を呼び出します。 これにより、最初のメモリ スナップショットが作成されます。

  2. プログラムがメモリ割り当て操作と割り当て解除操作を実行した後、別の CMemoryState オブジェクトを作成し、そのオブジェクトに対して Checkpoint を呼び出します。 これにより、メモリ使用量の 2 番目のスナップショットが取得されます。

  3. 3 番目の CMemoryState オブジェクトを作成し、その CMemoryState::D ifference メンバー関数を呼び出し、前の 2 つの CMemoryState オブジェクトを引数として指定します。 2 つのメモリ状態に違いがある場合、 Difference 関数は 0 以外の値を返します。 これは、一部のメモリ ブロックが割り当て解除されていないことを示しています。

    次の例は、コードがどのように見えるかを示しています。

    // Declare the variables needed
    #ifdef _DEBUG
        CMemoryState oldMemState, newMemState, diffMemState;
        oldMemState.Checkpoint();
    #endif
    
        // Do your memory allocations and deallocations.
        CString s("This is a frame variable");
        // The next object is a heap object.
        CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
    
    #ifdef _DEBUG
        newMemState.Checkpoint();
        if( diffMemState.Difference( oldMemState, newMemState ) )
        {
            TRACE( "Memory leaked!\n" );
        }
    #endif
    

    メモリ チェック ステートメントは #ifdef _DEBUG / #endif ブロックで囲まれているため、プログラムのデバッグ バージョンでのみコンパイルされます。

    メモリ リークが存在することがわかったので、別のメンバ関数 CMemoryState::D umpStatistics を使用して、メモリ リークを特定できます。

メモリ統計の表示

CMemoryState::D ifference 関数は、2 つのメモリ状態オブジェクトを調べ、開始状態と終了状態の間でヒープから割り当て解除されていないオブジェクトを検出します。 メモリ スナップショットを取得し、 CMemoryState::Difference を使用して比較した後、 CMemoryState::D umpStatistics を呼び出して、割り当てが解除されていないオブジェクトに関する情報を取得できます。

次の例を確認してください。

if( diffMemState.Difference( oldMemState, newMemState ) )
{
    TRACE( "Memory leaked!\n" );
    diffMemState.DumpStatistics();
}

この例のサンプル ダンプは、次のようになります。

0 bytes in 0 Free Blocks
22 bytes in 1 Object Blocks
45 bytes in 4 Non-Object Blocks
Largest number used: 67 bytes
Total allocations: 67 bytes

空きブロックは、 afxMemDFdelayFreeMemDF に設定されている場合に割り当て解除が遅延するブロックです。

2 行目に示されている通常のオブジェクト ブロックは、ヒープに割り当てられたままになります。

非オブジェクト ブロックには、 new で割り当てられた配列と構造体が含まれます。 この場合、4 つの非オブジェクト ブロックがヒープに割り当てられましたが、割り当て解除は解除されていません。

Largest number used は、プログラムがいつでも使用する最大メモリを提供します。

Total allocations プログラムが使用するメモリの合計量を示します。

オブジェクト・ダンプの取得

MFC プログラムでは、 CMemoryState::D umpAllObjectsSince を使用して、ヒープ上の割り当てが解除されていないすべてのオブジェクトの説明をダンプできます。 DumpAllObjectsSince 最後の CMemoryState::Checkpoint 以降に割り当てられたすべてのオブジェクトをダンプします。 Checkpoint呼び出しが行われていない場合、DumpAllObjectsSince は、現在メモリ内にあるすべてのオブジェクトと非オブジェクトをダンプします。

MFC オブジェクト ダンプを使用する前に、 診断トレースを有効にする必要があります

MFC は、プログラムの終了時にリークしたすべてのオブジェクトを自動的にダンプするため、その時点でオブジェクトをダンプするコードを作成する必要はありません。

次のコードは、2 つのメモリ状態を比較してメモリ リークをテストし、リークが検出された場合はすべてのオブジェクトをダンプします。

if( diffMemState.Difference( oldMemState, newMemState ) )
{
    TRACE( "Memory leaked!\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

ほとんどの行の先頭にある中括弧内の数字は、オブジェクトが割り当てられた順序を示しています。 最後に割り当てられたオブジェクトが最も大きい番号を持ち、ダンプの先頭に表示されます。

オブジェクト ダンプから最大限の情報を引き出すには、CObject派生オブジェクトの Dump メンバ関数をオーバーライドして、オブジェクト ダンプをカスタマイズできます。

特定のメモリ割り当てにブレークポイントを設定するには、グローバル変数 [ _afxBreakAlloc ] を中括弧に示されている数値に設定します。 プログラムを再実行すると、その割り当てが行われたときにデバッガーは実行を中断します。 その後、呼び出し履歴を調べて、プログラムがどのようにしてその時点に到達したかを確認できます。

C ランタイム ライブラリには、C ランタイム割り当てに使用できる同様の関数 _CrtSetBreakAlloc があります。

メモリダンプの解釈

このオブジェクトダンプを詳しく見てください。

{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

このダンプを生成したプログラムには、明示的な割り当てが 2 つだけあり、1 つはスタック上、もう 1 つはヒープ上にあります。

// Do your memory allocations and deallocations.
CString s("This is a frame variable");
// The next object is a heap object.
CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );

CPersonコンストラクタは、char へのポインタである 3 つの引数を受け取り、CStringメンバー変数を初期化するために使用されます。 メモリ ダンプには、 CPerson オブジェクトと 3 つの非オブジェクト ブロック (3、4、5) が表示されます。 これらは CString メンバー変数の文字を保持し、 CPerson オブジェクト デストラクタが呼び出されたときに削除されません。

ブロック番号 2 は、 CPerson オブジェクト自体です。 $51A4はブロックのアドレスを表し、その後にオブジェクトの内容が続きます。これは、DumpAllObjectsSince によって呼び出されたときに CPerson::Dump によって出力されました。

ブロック番号 1 は、そのシーケンス番号とサイズがフレームCString変数の文字数と一致するため、CString フレーム変数に関連付けられていると推測できます。 フレームに割り当てられた変数は、フレームがスコープ外になると自動的に割り当てが解除されます。

フレーム変数

一般に、フレーム変数に関連付けられたヒープ オブジェクトについては、フレーム変数がスコープ外に出ると自動的に割り当てが解除されるため、心配する必要はありません。 メモリ診断ダンプが乱雑にならないようにするには、 Checkpoint への呼び出しをフレーム変数のスコープ外に配置する必要があります。 たとえば、次に示すように、前の割り当てコードをスコープの角かっこで囲みます。

oldMemState.Checkpoint();
{
    // Do your memory allocations and deallocations ...
    CString s("This is a frame variable");
    // The next object is a heap object.
    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など) であり、一部は非オブジェクト割り当てであることに注意してください。 「非オブジェクト割り当て」は、charintlong などのプリミティブ C タイプのCObjectまたは割り当てから派生していないオブジェクトへの割り当てです。 CObject の派生クラスが内部バッファーなどの追加の領域を割り当てる場合、それらのオブジェクトにはオブジェクトと非オブジェクトの両方の割り当てが表示されます。

メモリリークの防止

上記のコードでは、 CString フレーム変数に関連付けられているメモリ ブロックが自動的に割り当て解除されており、メモリ リークとして表示されないことに注意してください。 スコープ・ルールに関連付けられた自動割り当て解除は、フレーム変数に関連付けられたほとんどのメモリー・リークを処理します。

ただし、ヒープに割り当てられたオブジェクトの場合は、メモリ リークを防ぐために、オブジェクトを明示的に削除する必要があります。 前の例の最後のメモリ リークをクリーンアップするには、次のようにヒープに割り当てられた CPerson オブジェクトを削除します。

{
    // Do your memory allocations and deallocations.
    CString s("This is a frame variable");
    // The next object is a heap object.
    CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );
    delete p;
}

オブジェクト・ダンプのカスタマイズ

CObject からクラスを派生させる場合は、DumpAllObjectsSince を使用してオブジェクトを出力ウィンドウにダンプするときに、Dump メンバー関数をオーバーライドして追加情報を提供できます。

Dump 関数は、オブジェクトのメンバー変数のテキスト表現をダンプ コンテキスト (CDumpContext) に書き込みます。 ダンプ・コンテキストは、入出力ストリームと似ています。 追加演算子 (<<) を使用して、データを CDumpContextに送信できます。

Dump 関数をオーバーライドする場合は、最初に基本クラス バージョンの Dump を呼び出して、基本クラス オブジェクトの内容をダンプする必要があります。 次に、派生クラスの各メンバー変数のテキストによる説明と値を出力します。

Dump関数の宣言は、次のようになります。

class CPerson : public CObject
{
public:
#ifdef _DEBUG
    virtual void Dump( CDumpContext& dc ) const;
#endif

    CString m_firstName;
    CString m_lastName;
    // And so on...
};

オブジェクト ダンプはプログラムのデバッグ時にのみ意味をなすため、 Dump 関数の宣言は #ifdef _DEBUG / #endif ブロックで囲まれます。

次の例では、 Dump 関数は最初に基本クラスの Dump 関数を呼び出します。 次に、各メンバー変数の簡単な説明とメンバーの値を診断ストリームに書き込みます。

#ifdef _DEBUG
void CPerson::Dump( CDumpContext& dc ) const
{
    // Call the base class function first.
    CObject::Dump( dc );

    // Now do the stuff for our specific class.
    dc << "last name: " << m_lastName << "\n"
        << "first name: " << m_firstName << "\n";
}
#endif

ダンプ出力の送信先を指定するには、 CDumpContext 引数を指定する必要があります。 MFC のデバッグ バージョンには、デバッガーに出力を送信する afxDump という名前の定義済みの CDumpContext オブジェクトが用意されています。

CPerson* pMyPerson = new CPerson;
// Set some fields of the CPerson object.
//...
// Now dump the contents.
#ifdef _DEBUG
pMyPerson->Dump( afxDump );
#endif

MFC デバッグ ビルドのサイズの縮小

大規模な MFC アプリケーションのデバッグ情報は、多くのディスク領域を占有する可能性があります。 次のいずれかの手順を使用して、サイズを縮小できます。

  1. /Z7 ではなく、/Z7、/Zi、/ZI (デバッグ情報形式) オプションを使用して MFC ライブラリをリビルドします。 これらのオプションにより、ライブラリ全体のデバッグ情報を含む 1 つのプログラム データベース (PDB) ファイルが構築されるため、冗長性が減り、領域が節約されます。

  2. デバッグ情報なしで MFC ライブラリを再構築します ( /Z7、/Zi、/ZI (デバッグ情報形式) オプションなし)。 この場合、デバッグ情報が不足しているため、MFC ライブラリ コード内のほとんどのデバッガー機能を使用できなくなりますが、MFC ライブラリは既に完全にデバッグされているため、これは問題にならない可能性があります。

  3. 以下で説明するように、選択したモジュールのデバッグ情報のみを使用して独自のアプリケーションをビルドします。

選択したモジュールのデバッグ情報を使用した MFC アプリのビルド

選択したモジュールを MFC デバッグ ライブラリでビルドすると、それらのモジュールでステップ実行やその他のデバッグ機能を使用できるようになります。 この手順では、プロジェクトのデバッグ構成とリリース構成の両方を使用するため、次の手順で説明する変更が必要になります (また、完全なリリース ビルドが必要な場合は "すべて再構築" が必要になります)。

  1. ソリューション エクスプローラーで、プロジェクトを選択します。

  2. 「表示」メニューから、「プロパティ・ページ」を選択します。

  3. まず、新しいプロジェクト構成を作成します。

    1. [<プロジェクト> プロパティ ページ] ダイアログ ボックスで、[構成マネージャー] ボタンをクリックします。

    2. [構成マネージャー] ダイアログ ボックスで、グリッド内でプロジェクトを見つけます。 [構成] 列で、[新規<...>] を選択します。

    3. [新しいプロジェクト構成] ダイアログ ボックスの [プロジェクト構成名] ボックスに、新しい構成の名前 ("Partial Debug" など) を入力します。

    4. [ Copy Settings from ] リストで、[ Release] を選択します。

    5. [OK] をクリックして [新しいプロジェクト構成] ダイアログ ボックスを閉じます。

    6. [Configuration Manager] ダイアログ ボックスを閉じます。

  4. 次に、プロジェクト全体のオプションを設定します。

    1. [プロパティ ページ] ダイアログ ボックスの [構成プロパティ] フォルダで、[全般] カテゴリを選択します。

    2. プロジェクト設定グリッドで、[ プロジェクトの既定値] を展開します (必要な場合)。

    3. [プロジェクトの既定値] で、[MFC の使用] を見つけます。 現在の設定は、グリッドの右側の列に表示されます。 現在の設定をクリックし、[ スタティック ライブラリで MFC を使用する] に変更します。

    4. [プロパティ ページ] ダイアログ ボックスの左側のウィンドウで、[C/C++] フォルダーを開き、[プリプロセッサ] を選択します。 プロパティ グリッドで [Preprocessor Definitions ] を見つけ、"NDEBUG" を "_DEBUG" に置き換えます。

    5. [プロパティ ページ] ダイアログ ボックスの左側のウィンドウで、[リンカー] フォルダーを開き、[入力カテゴリ] を選択します。 プロパティ グリッドで、 追加の依存関係 を見つけます。 [追加の依存関係] 設定に「NAFXCWD.LIB」と「LIBCMT」

    6. [OK] をクリックして新しいビルド オプションを保存し、[プロパティ ページ] ダイアログ ボックスを閉じます。

  5. [ビルド] メニューから [リビルド] を選択します。 これにより、モジュールからすべてのデバッグ情報が削除されますが、MFC ライブラリには影響しません。

  6. 次に、アプリケーション内の選択したモジュールにデバッグ情報を追加し直す必要があります。 ブレークポイントを設定し、他のデバッガー機能を実行できるのは、デバッグ情報を使用してコンパイルしたモジュール内のみであることに注意してください。 デバッグ情報を含めるプロジェクト・ファイルごとに、以下のステップを実行します。

    1. ソリューション エクスプローラーで、プロジェクトの下にある [ソース ファイル ] フォルダーを開きます。

    2. デバッグ情報を設定するファイルを選択します。

    3. 「表示」メニューから、「プロパティ・ページ」を選択します。

    4. [プロパティ ページ] ダイアログ ボックスの [構成設定] フォルダーで、[C/C++] フォルダーを開き、[全般] カテゴリを選択します。

    5. プロパティ グリッドで、 デバッグ情報形式を見つけます。

    6. [デバッグ情報形式] 設定をクリックし、デバッグ情報に必要なオプション (通常は /ZI) を選択します。

    7. アプリケーション ウィザードで生成されたアプリケーションを使用している場合、またはプリコンパイル済みヘッダーがある場合は、他のモジュールをコンパイルする前に、プリコンパイル済みヘッダーをオフにするか、再コンパイルする必要があります。 それ以外の場合は、警告 C4650 とエラー メッセージ C2855 が表示されます。 プリコンパイル済みヘッダーをオフにするには、<Project> [プロパティ] ダイアログ ボックス ([構成プロパティ] フォルダー、C/C++ サブフォルダー、[プリコンパイル済みヘッダー] カテゴリ) で [プリコンパイル済みヘッダーの作成/使用] 設定を変更します。

  7. [ビルド] メニューから [ビルド] を選択して、古いプロジェクト ファイルを再構築します。

    このトピックで説明する手法の代わりに、外部 makefile を使用して、各ファイルに対して個別のオプションを定義することもできます。 その場合、MFC デバッグ ライブラリとリンクするには、モジュールごとに _DEBUG フラグを定義する必要があります。 MFC リリース ライブラリを使用する場合は、NDEBUG を定義する必要があります。 外部 Makefile の記述の詳細については、「 NMAKE リファレンス」を参照してください。