【MFC】IsBadReadPtr / IsBadWritePtr は使うな

【MFC】IsBadReadPtr / IsBadWritePtr は使うな

レガシーな MFC コードの中に IsBadReadPtrIsBadWritePtr を使ったポインタチェックが残っていることがあります。一見安全そうに見えますが、Microsoft はこれらの関数を使わないことを推奨しています。
この記事では、なぜ使ってはいけないのかと、代わりに何をすべきかを解説しています。

IsBadReadPtr の非推奨理由と代替手段を検証するダイアログの初期画面

目次

なぜ非推奨なのか

IsBadReadPtr / IsBadWritePtr は、指定されたメモリ範囲にアクセスできるかを検査する関数です。内部的には構造化例外処理(SEH)でアクセス違反を捕捉して結果を返します。

この仕組みには、致命的な問題が 3 つあります。

問題 1:ガードページを踏み潰す

Windows はスレッドごとのスタック拡張や一部の保護にガードページPAGE_GUARD)を使っています。IsBadReadPtr のような「読めるか試す」API は、その保護ページに触れて例外を消費する可能性があり、特に別スレッドのスタック拡張タイミングと競合すると挙動を壊します。

問題 2:マルチスレッドで競合する

IsBadReadPtrFALSE(読み取り可能)を返した瞬間に、別のスレッドがそのメモリを解放するかもしれません。チェックした時点と使う時点で状態が異なる(TOCTOU: Time of Check to Time of Use)ため、結果を信頼できません。

問題 3:バグを隠す

不正なポインタが渡されたということは、すでにバグが存在しています。IsBadReadPtr でチェックして「ダメだったら何もしない」としても、バグの根本原因は放置されたままです。問題を隠してしまうことで、デバッグが困難になります。


Microsoft の公式見解

Microsoft Learn の IsBadReadPtr のページには、明確に次の記述があります。

This function is obsolete and should not be used. Despite its name, it does not guarantee that the pointer is valid or that the memory pointed to is safe to use.

「名前に反して、ポインタが有効であることも、メモリが安全に使えることも保証しない」と断言されています。

IsBadReadPtr が信頼できない結果を返す様子と SEH による安全なフォールバックのログ出力

代わりに何をすべきか

ポインタの正当性をランタイムで検査しようとするのではなく、不正なポインタが渡されないコード設計にすることが正解です。

対策 1:ASSERT で前提条件を明示する

void ProcessData(const MyStruct* pData)
{
    ASSERT(pData != nullptr);  // Debug ビルドで不正呼び出しを検出
    if (pData == nullptr)
        return;

    // 本処理
    pData->DoSomething();
}

ASSERT は Debug ビルドで違反を即座に報告します。Release ビルドでは消えるため、パフォーマンスへの影響はありません。

対策 2:構造化例外処理(SEH)で保護する

どうしてもサードパーティのコールバックなど、信頼できないポインタを受け取る場面では、__try / __except で保護する方法があります。

__try
{
    // 信頼できないポインタを使う処理
    result = pCallback->Execute();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
    // アクセス違反が発生した場合のフォールバック
    TRACE(_T("Callback caused access violation\n"));
    result = E_FAIL;
}

ただし、これは最終手段です。SEH を多用するコードは、問題の隠蔽につながりやすいため注意してください。

対策 3:スマートポインタを使う

新規コードでは std::unique_ptrstd::shared_ptr を使うことで、そもそもダングリングポインタが発生しにくい設計にできます。

// 所有権が明確で、解放忘れがない
auto pData = std::make_unique<MyStruct>();
ProcessData(pData.get());

既存コードから IsBadReadPtr を除去する手順

  1. ソリューション全体で IsBadReadPtr / IsBadWritePtr / IsBadStringPtr / IsBadCodePtr を検索する
  2. 各呼び出し箇所で、なぜそのチェックが必要だったのかを調べる(不正ポインタの発生源を特定する)
  3. 発生源を修正できるなら修正し、チェックを ASSERT(ptr != nullptr) + nullptr チェックに置き換える
  4. 発生源を修正できない場合(外部ライブラリ等)は、SEH で保護する

まとめ

  • IsBadReadPtr / IsBadWritePtr は Microsoft が公式に非推奨としています
  • ガードページの破壊、TOCTOU 競合、バグの隠蔽という 3 つの致命的問題があります
  • 代替手段は ASSERT + nullptr チェック、必要に応じて SEH、新規コードではスマートポインタです
  • ポインタの正当性は「検査する」のではなく「そもそも不正にならない設計」で担保してください
目次