【MFC】Debug Assertion Failed! の正しい読み方

【MFC】Debug Assertion Failed! の正しい読み方

Debug ビルドで実行中に突然「Debug Assertion Failed!」というダイアログが表示されて、何をどう読めばいいか分からず閉じてしまった、という経験は珍しくありません。

この記事では、このダイアログに表示される情報の読み方と、ソースコード上の原因箇所を即座に特定する手順を整理しています。


目次

最初に見る場所:ダイアログの 3 行

Assertion Failed ダイアログには、原因特定に必要な情報がすべて書かれています。まずこの 3 行を読みます。

項目意味見方
FileASSERT が書かれているソースファイルのフルパスMFC 内部のファイルか、自分のコードかを確認する
LineASSERT マクロが置かれている行番号File と合わせてソースを開く手がかりになる
Expression失敗した条件式そのもの何が FALSE だったかがここに出る

たとえば次のように表示されたとします。

Debug Assertion Failed!

File: f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp
Line: 981
Expression: pMap != NULL

これは「wincore.cpp の 981 行目にある ASSERT(pMap != NULL) が失敗した」、つまり pMapNULL だった、という意味です。


ダイアログのボタンの意味

ダイアログには 3 つのボタンがあります。押す順番を間違えると原因箇所を見失います。

ボタン動作推奨
中止 (Abort)アプリを即座に終了する原因を調べたいなら押さない
再試行 (Retry)デバッガーで停止する(ブレーク)最初に押すべきボタン
無視 (Ignore)ASSERT を無視して続行する不安定な状態で続行するため非推奨

「再試行 (Retry)」を押してください。Visual Studio のデバッガーが ASSERT の行で停止し、コールスタックと変数の値をその場で確認できます。

「再試行」を押した後、Visual Studio のデバッガーが ASSERT 行で停止した状態。コールスタックウィンドウに呼び出し履歴が見えている

File が MFC 内部のソースだった場合

ダイアログの File が wincore.cppviewcore.cppdlgcore.cpp のような MFC 内部のファイルを示していることがあります。これは MFC のバグではなく、自分のコードが MFC に不正な状態を渡した結果です。

この場合は、コールスタック(呼び出し履歴)を下に辿って、自分のコードが最初に現れる行を探してください。そこが実際の原因箇所です。

  1. 「再試行」でデバッガーに入る
  2. コールスタックウィンドウを開く(デバッグ → ウィンドウ → 呼び出し履歴、または Ctrl + Alt + C
  3. スタックの上から下へ順に見て、自分のソースファイル名が出てくるフレームをダブルクリック
  4. その行の前後で、NULL になっている変数や不正な値を渡している箇所を確認する

コールスタックの読み方について詳しくは、No.25「Call Stack (呼び出し履歴) の歩き方」を参照してください。


よくある Assertion 失敗パターン

MFC の Debug ビルドでよく遭遇する Assertion のパターンを整理します。Expression の文字列で原因を絞り込めます。

pWnd != NULL / pMap != NULL

ウィンドウハンドル(HWND)から MFC オブジェクトへの対応が見つからない状態です。

  • OnInitDialog より前にコントロールにアクセスしている
  • ダイアログが閉じたあとにメンバー変数のコントロールを操作している
  • GetDlgItem の戻り値を確認せず使っている

afxCurrentInstanceHandle != NULL

MFC のモジュール状態が正しくセットされていません。DLL 内で MFC を使っている場合によく発生します。

  • DLL のエクスポート関数の先頭に AFX_MANAGE_STATE(AfxGetStaticModuleState()) が抜けている

IsWindow(m_hWnd)

すでに破棄されたウィンドウのハンドルを使おうとしています。

  • ダイアログを DestroyWindow した後にメンバー関数を呼んでいる
  • タイマーコールバックが、ウィンドウ破棄後に走っている

nIndex >= 0 && nIndex < m_nSize

MFC のコレクションクラス(CArray / CObArray など)で、配列の範囲外にアクセスしています。

  • ループの終了条件が GetSize() を超えている
  • 要素を削除したあとにインデックスを更新していない

Release ビルドでは出ない理由

ASSERT マクロは Debug ビルドでのみ有効です。Release ビルドではプリプロセッサによって完全に消えます。

// MFC の ASSERT マクロ(簡略化)
#ifdef _DEBUG
  #define ASSERT(f) \
    do { if (!(f)) AfxAssertFailedLine(THIS_FILE, __LINE__); } while (0)
#else
  #define ASSERT(f) ((void)0)
#endif

Release では ASSERT が消えるため、エラーダイアログは出ませんが、問題の根本原因が消えるわけではありません。NULL ポインタアクセスや配列の範囲外アクセスは、Release ビルドではクラッシュや未定義動作として現れます。

Debug で ASSERT が出たら、Release でも必ず起きている問題だと考えて、原因を修正してください。


出力ウィンドウの ASSERT メッセージ

ダイアログと同時に、Visual Studio の出力ウィンドウにもメッセージが出力されます。

Debug Assertion Failed!
  File: wincore.cpp
  Line: 981

For more information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.
Visual Studio の出力ウィンドウに ASSERT failure のメッセージが表示されている状態

ダイアログを誤って「中止」で閉じてしまった場合でも、出力ウィンドウにファイル名と行番号が残っています。ここから該当ソースを特定できます。


ASSERT を自分のコードに仕込む

MFC が内部で使っているのと同じ ASSERT マクロは、自分のコードでも使えます。「ここに NULL が来たらおかしい」「この値は正の整数のはず」という前提条件をコードに埋め込んでおくと、不正な状態を早期に発見できます。

void CMyDialog::SetItemCount(int nCount)
{
    // nCount が負ならバグ。Debug で即停止させる
    ASSERT(nCount >= 0);

    m_nItemCount = nCount;
    UpdateList();
}

ASSERT の詳しい活用法については、No.23「ASSERT(条件) でバグを早期発見する」を参照してください。


まとめ

  • [ ] ダイアログの File / Line / Expression を必ず読む。特に Expression が原因のヒント
  • [ ] 「再試行 (Retry)」を押してデバッガーに入り、コールスタックから自分のコードを探す
  • [ ] File が MFC 内部のソースなら、コールスタックを下に辿って自分の呼び出し元を特定する
  • [ ] Release ビルドでは ASSERT は消えるが、問題自体は消えない。Debug で出たら必ず修正する
目次