【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] に書き込まれますが、配列の有効なインデックスは0〜4です。1要素分の書き込みでも、配列の直後にあるガードバイトを上書きします。

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

APIに渡すバッファサイズの単位を間違えるケースです。

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:その変数への書き込み箇所をすべて洗い出す

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

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

実際には、壊した行そのものではなく、その直後の行や関数を抜けるタイミングで停止することがあります。下の例でも、原因はmemcpyの行ですが、Visual Studioで停止するのはその直後です。

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で出たうちに直します

このダイアログが検出するのは、スタック上のローカル変数の破壊です。後のnewやdeleteで落ちるなど、ヒープの破壊が疑われるときは、疑わしい処理の前後で調べる範囲を絞る方法を「【MFC】_CrtCheckMemoryでメモリ破壊の発生区間を挟み込む」で確かめられます。

目次