Article
C++26で未定義動作はどう減る?診断と実行時検査の境界
C++26は、不完全型のdeleteや一時オブジェクトへの参照返却を不適格にし、一部の未初期化読み取りにはerroneous behaviourを導入します。コンパイラの診断と標準ライブラリのhardeningを分け、既存コードで得られる効果と残る条件を整理します。
Share
こはるの読みどころ
「C++26対応」だけで判断せず、コンパイラが見つける誤りと、ライブラリの設定で検査する誤りを分けて読んでみよう。

C++26で未定義動作が減ると聞くと、既存コードを再ビルドするだけで、どこまで問題を見つけられるのかが気になります。ISO C++ Blogで紹介されたSandor Dargoの解説は、その問いを考えるきっかけになります。
ただ、誤ったプログラムをコンパイル時に診断する仕組みと、実行中に不正な操作を検査する仕組みでは、効く場所が違います。未初期化値のように、誤りの扱い自体が変わるケースもあります。
この違いを押さえると、C++26への切り替えで見つかる問題と、コードやビルド設定で手当てする部分を分けて判断できます。
型と寿命から分かる誤りをコンパイル時に診断する
たとえば、クラスを前方宣言しただけの場所で、そのポインタにdeleteを適用する場面です。その位置では、デストラクタやクラス固有の解放関数の有無を判断するための定義が見えていません。C++26のP3144R2は、このような不完全クラス型を通した削除をill-formed、つまり規格上不適格にします。
従来は、完全な型が非自明なデストラクタやクラス固有の解放関数を持つ場合に未定義動作となり、それらがない場合は許されていました。新しい規則では、この例外もなくなります。対処は、削除処理をクラス定義が見える位置へ移すことです。実装を隠しているなら、削除を担う関数の定義を実装ファイルに置く方法が考えられます。
同じく型から見つけられるのが、戻り値の参照を一時オブジェクトへ束縛する誤りです。P2748R5が対象にするのは、returnでそのような参照を初期化するケースです。たとえばconst double&を返す関数で、static intの変数を返しても、変換によって一時的なdoubleが作られます。元の整数が長生きしていても、変換後の一時オブジェクトは別物ですね。
この場合は、値として返す設計を検討できます。変更が防ぐのは特定の参照束縛であり、あらゆるダングリング参照を検出するわけではありません。また、ill-formedで求められるのは規格違反の診断です。診断後も拡張として受理する実装はあり得るため、「どの設定でも必ずコンパイルが停止する」とまでは言えません。
未初期化のintは診断しやすくなるが、初期化は必要
型の組み合わせだけでは決まらず、実行経路によって誤りになるものもあります。その代表が、値を代入する前のローカル変数を読む処理です。
P2795R5では、通常の自動記憶域期間を持つ未初期化のintなどにerroneous valueを与え、その読み取りをerroneous behaviourとして扱います。これは誤った動作であり、実装による診断が推奨されます。動作規則では実行の終了も許されており、必ず警告が出る保証や、処理を継続できる保証ではありません。
ここは、記憶域期間と属性で扱いが分かれます。値の区分を定める規則に照らすと、関数内の次の例は同じ意味ではありません。
| 宣言と、その後の読み取り | C++26での扱い |
|---|---|
int count;を代入前に読む |
erroneous behaviour |
int count [[indeterminate]];を代入前に読む |
未定義動作 |
new intで確保した整数を代入前に読む |
未定義動作 |
int count{};を読む |
初期化された0を読む |
この表はintに絞った比較です。unsigned charやstd::byteの一部の操作には別の規則があります。値が0に見える実装でも、未初期化の読み取りを正しい処理として扱わず、必要な初期値をコードに書く判断は変わりません。
添字の範囲外アクセスはライブラリの検査設定で捉える
一方、コンテナの要素数と添字の関係は、実行して初めて分かることがあります。標準ライブラリのhardeningを定めるP3471R4は、対象操作の前提条件を検査する枠組みを導入します。たとえば、要素が3個のstd::vectorに添字3でアクセスすると、必要なindex < size()を満たしません。空のstd::optionalの逆参照も対象です。
hardened implementationでは、この違反が契約違反として扱われます。ただし、非強化実装では前提条件違反が未定義動作のまま残り、非終了型の契約違反ハンドラが戻った場合も未定義動作になります。検査が入ったからといって、不正なアクセスから有効な値を返せるようになるわけではありません。
実際の有効化方法は実装ごとに異なります。libc++のHardening Modesでは、たとえばコンパイルオプションに-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FASTを指定してモードを選びます。これはlibc++を使う構成の設定で、既定値は配布元へ確認する必要があります。利用側のマクロ指定だけでは、配布済みのコンパイル済みライブラリ部分の設定までは変わりません。
検査量と実行コストにはトレードオフがあります。libc++はfastを本番向け、debugを本番以外向けとしているので、選んだモードで自分の処理を測るのが筋です。全環境に共通の性能低下率を置いて判断することはできません。
C++26モードと個別機能の対応状況を照合する
ここまでの違いは、移行時の確認方法にもつながります。2026年10月10日の調査時点で、GCCの対応表とClangの対応表は、言語側の機能を次のように記載しています。
| 言語機能 | GCCの対応表 | Clangの対応表 |
|---|---|---|
| 一時オブジェクトへの参照返却の制限(P2748R5) | 14 | 19 |
| 不完全型の削除の禁止(P3144R2) | 15 | 19 |
| 未初期化読み取りのerroneous behaviour(P2795R5) | 16 | 未対応 |
これは各機能の実装状況であり、各バージョンがC++26全体に対応しているという表ではありません。まず手元のコンパイラと選択した言語モードを対応表に照らし、今回対象になった削除処理や参照返却の診断を確認すると、修正箇所を絞れます。次に、ライブラリ側のhardeningが有効かを別に調べます。
冒頭の「再ビルドでどこまで見つかるか」への答えは、実装済みの言語規則と、有効にした実行時検査の範囲までです。C++26の変更を実務で生かすには、診断された型・寿命の誤りを直し、値の初期化を明示し、ライブラリの検査条件をそろえることがつながります。規格の改善を、実際のビルドで問題を発見する機会へ変えていきたいですね。
出典
- Title: C++26: Reducing undefined behaviour -- Sandor Dargo
- URL: https://isocpp.org//blog/2026/10/cpp26-reducing-undefined-behaviour-sandor-dargo
Share
Related Articles
カテゴリやタグが近い記事を続けて読めるように並べています。




