【MFC】Call Stack (呼び出し履歴) の歩き方

【MFC】Call Stack (呼び出し履歴) の歩き方

クラッシュで停止したとき、黄色い矢印の行だけを見ても原因に届かないことがあります。

この記事では、Call Stack を使って呼び出し元へ遡り、不正な値が入った場所を確認する手順をまとめます。


目次

Call Stack (呼び出し履歴) の役割

プログラムが関数を呼び出すたびに、CPUは「どの関数から呼ばれたのか(終わったらどこに戻るのか)」という情報を、「スタック」というメモリ領域に順番に積み上げて(Pushして)いきます。
この積み上がった履歴を一覧表示してくれるのが Call Stack ウィンドウです。

開き方と見方

  1. デバッグ中に停止(ブレークポンイト、またはクラッシュ)した状態で、Visual Studioのメニューから 「デバッグ(Debug)」 -> 「ウィンドウ(Windows)」 -> 「呼び出し履歴(Call Stack)」 を開きます。(ショートカット: Ctrl + Alt + C)
  2. ウィンドウには、関数名がズラリと並びます。
    一番上の行が「現在停止している関数」であり、下に行くほど「過去(呼び出し元)」に遡ります。
Visual Studio の呼び出し履歴(Call Stack)ウィンドウのスクリーンショット。一番上の関数から下へ向かって呼び出し元が続いている様子

Call Stack の使い方(原因箇所の探し方)

例えば、関数が三段にネストしていて、一番奥でアクセス違反が起きたとします。

void CrashFunctionC()
{
    int* p = NULL;
    *p = 999;
}

void CrashFunctionB()
{
    CrashFunctionC();
}

void CrashFunctionA()
{
    CrashFunctionB();
}

停止した直後に見えるのは、一番上の CrashFunctionC() です。ここだけ見ても「NULLポインタを書いた」という事実しか分かりません。

原因を追うため、Call Stack ウィンドウで一段下(呼び出し元)の行をダブルクリックします。

すると、ソースコードのエディタが CrashFunctionB()CrashFunctionA() に順に戻ります。さらに重要なのは、選んだフレームの時点に合わせてローカル変数の表示も切り替わることです。

この順番で戻ると、「どこからこのクラッシュ経路に入ったか」を落ち着いて追えます。黄色い矢印の行だけを見て終わらせないための道具が Call Stack です。

これが、Call Stack を使って呼び出し元へ遡る基本的な確認手順です。


MFC/Windows API の呼び出しを見分ける

MFCやWindowsデスクトップアプリをデバッグしていると、Call Stackの途中に、グレーの文字で大量の見慣れない関数が並んでいることがあります。

> MyProject.exe!CMyView::OnLButtonDown() 行 123
  mfc140d.dll!CWnd::OnWndMsg()
  mfc140d.dll!CWnd::WindowProc()
  mfc140d.dll!AfxCallWndProc()
  user32.dll!DispatchMessageW()
  MyProject.exe!CWinThread::PumpMessage()
  ...

これは、Windows(user32.dll)がメッセージを送り、MFC フレームワーク(mfc140.dll)を経由して、最終的に OnLButtonDown が呼ばれた経路を示しています。

これらMFC本体やOS内部の処理バグであることは極めて稀ですので、基本的には 「自分が書いたソースコード(黒字で表示されていて、右端に行番号が出ている行)」 だけを飛び石のようにダブルクリックして遡り、「自分自身のコードが、どのUI操作から始まったのか」を確認するのに使います。


まとめ

  • [ ] クラッシュした行だけでは足りない。呼び出し元から不正なデータの流れを確認する。
  • [ ] Call Stack ウィンドウ(Ctrl+Alt+C)を開き、一つ下(呼び出し元)をダブルクリックして過去へ遡る。
  • [ ] 遡った先で変数の状態を確認し、不正な引数がどこで入ったかを特定する。
  • [ ] MFCやWindowsのシステム関数(グレーの行)は読み飛ばし、自分が書いたコード(黒字の行)だけを追う。
目次