【MFC】Run-Time Check Failure #2 – Stack around the variable was corrupted

【MFC】Run-Time Check Failure #2 - Stack around the variable was corrupted

Debug ビルドで実行したら突然「Run-Time Check Failure #2 – Stack around the variable ‘…’ was corrupted」というダイアログが出て止まった、という状況は珍しくありません。
この記事では、このエラーが何を検知しているのかと、原因の特定から修正までの手順を解説します。


目次

このエラーの正体:スタック上のローカル変数が壊された

「Run-Time Check Failure #2」は、Visual C++ のランタイムチェック機能(/RTC1)がスタック破壊を検知したときに表示されます。Debug ビルドで /RTCs が有効な場合、ローカル変数のオーバーラン/アンダーランやスタックポインター破損を検出できるようになっており、関数を抜ける前後で異常が見つかると「変数の周辺のスタックが壊れた」と報告します。

つまり、このエラーが出た時点でローカル変数の領域をはみ出す書き込みが確実に起きています。


原因の典型パターン

このエラーの原因は、ほぼ次の 3 パターンに絞られます。

パターン 1:固定長配列のオーバーラン

もっとも多い原因です。ローカルに確保した配列のサイズを超えて書き込んでいます。

void OverrunFixedArray()
{
    char buf[10];
    strcpy_s(buf, _countof(buf), "OK");   // 安全な書き込み

    const char src[] = "Hello World!";  // 13 bytes
    memcpy(buf, src, sizeof(src));       // 10 バイト領域に 13 バイト書き込む
}

buf は 10 バイトしかないのに、src には "Hello World!"(終端 '\0' を含めて 13 バイト)が入っています。memcpy は境界チェックをしないため、そのまま 3 バイトぶん配列末尾を越えて書き込みます。

パターン 2:ループのインデックスミス

配列のサイズと終了条件が合っていないケースです。

void BadLoop()
{
    int data[5];
    for (int i = 0; i <= 5; i++)   // ← i < 5 が正しい
    {
        data[i] = i * 10;
    }
}

i <= 5 により data[5] に書き込まれますが、配列の有効なインデックスは 04 です。1 要素分のオーバーランでもガードバイトは確実に踏み越えます。

パターン 3:Win32 API へのバッファ渡しミス

API に渡すバッファサイズの単位を間違えるケースです。MFC / Win32 開発では特に多く見られます。

void BadApiCall()
{
    TCHAR path[MAX_PATH];
    // 第2引数は「文字数」だが、バイト数を渡してしまう例
    GetModuleFileName(NULL, path, sizeof(path));
    // ← Unicode ビルドでは sizeof = MAX_PATH * 2 → オーバーラン
}

Unicode ビルドでは TCHAR は 2 バイトです。sizeof(path)MAX_PATH * 2 を返すため、API が MAX_PATH 文字を超えて書き込む可能性があります。正しくは _countof(path) または MAX_PATH を直接渡します。

// 正しい例
GetModuleFileName(NULL, path, _countof(path));

原因の特定手順

エラーダイアログに変数名が表示されます。これが最大のヒントです。

手順 1:エラーダイアログの変数名を確認する

ダイアログには「Stack around the variable ‘buf‘ was corrupted」のように、壊された変数の名前が表示されます。この変数が宣言されている関数を開いてください。

Microsoft Visual C++ Runtime Library のダイアログで「Run-Time Check Failure #2 - Stack around the variable 'buf' was corrupted」と表示されている画面

このメッセージは、どの変数の周辺が壊れたかをかなり具体的に教えてくれます。まずは変数名を起点に、同じ関数の中でその変数へ書き込んでいる箇所を絞り込むのが最短です。

手順 2:その変数への書き込み箇所をすべて洗い出す

該当関数内で、その変数に対するすべての書き込み操作(代入、memcpystrcpy、API 呼び出しなど)をリストアップします。特に次の点を確認してください。

  • 配列の宣言サイズと、書き込み時に使っている上限値が一致しているか
  • ループの終了条件が <(未満)ではなく <=(以下)になっていないか
  • API に渡すバッファサイズの単位が「バイト数」なのか「文字数」なのか

実際には、壊した行そのものではなく、その直後の行や関数を抜けるタイミングで停止することがあります。下の例でも、原因は memcpy ですが、Visual Studio 上ではその直後の流れで Run-Time Check Failure #2 が表面化しています。

Visual Studio で固定長配列 buf に対する memcpy の直後に停止し、Run-Time Check Failure #2 のポップアップが表示されている画面

手順 3:データブレークポイントで書き換え箇所を特定する

目視で見つからない場合は、データブレークポイントが有効です。ガードバイト領域のアドレスにデータブレークポイントを設定すると、そこに書き込んだ瞬間に停止します。

具体的な手順は、ウォッチウィンドウで &buf + sizeof(buf)(配列末尾の直後)のアドレスを確認し、そのアドレスに 4 バイトのデータブレークポイントを設定します。


ランタイムチェックの仕組みと設定

このエラーを報告する機能は、プロジェクトのプロパティで制御されています。

C/C++ → コード生成 → 基本ランタイム チェックを確認してください。Debug 構成では既定で「両方 (/RTC1)」が設定されています。

設定画面では、次のように 「両方 (/RTC1)」 が選択されていれば問題ありません。

Visual Studio のプロジェクトプロパティで C/C++ のコード生成にある基本ランタイム チェックが「両方 (/RTC1)」になっている画面

/RTC1/RTCs/RTCu をまとめて有効にする設定です。今回のようなスタック破壊の検出には、/RTCs が効いています。

オプション検出内容
/RTCsローカル変数のオーバーラン/アンダーラン検出、スタックポインター検証
/RTCu未初期化ローカル変数の使用
/RTC1上記の両方(/RTCsu 相当)

※ Release ビルドでは /RTC は無効です(最適化 /O1 /O2 と併用できません)。つまり、Release で同じコードを実行すると検知されずに動作し続け、ある日突然クラッシュするという形で顕在化します。Debug で出たこのエラーは必ず修正してください。


修正の方針

原因が特定できたら、修正は単純です。

原因修正
固定長配列が小さすぎるサイズを十分に確保する、または std::vector / CString に置き換える
ループの <=< に修正する
API のバッファサイズ単位sizeof_countof に修正する

根本的な対策として、固定長の char/TCHAR 配列をやめて std::vector<TCHAR>CString に移行すると、この種のエラーは発生しなくなります。新規コードでは積極的に検討してください。


まとめ

  • 「Run-Time Check Failure #2」は Debug ビルドの /RTCs がスタック破壊を検知したエラーです
  • エラーダイアログに表示される変数名が原因箇所の最大のヒントです
  • 原因は配列オーバーラン・ループ境界ミス・API バッファサイズ単位の間違いのいずれかです
  • Release ビルドでは検知されないため、Debug で出たこの警告は必ず対処してください
目次