最終更新日時:
が更新

履歴 編集

未定義動作とIFNDRの一覧を規格の付録に追加 [P3596R3](C++29)

このページはC++29に採用された言語機能の変更を解説しています。

のちのC++規格でさらに変更される場合があるため関連項目を参照してください。

概要

未定義動作の規定や、不適格だが診断が要求されない (ill-formed, no diagnostic required、IFNDR) 規則は、規格の本文全体に散在しており、その全体像を把握することが難しかった。

C++29では、これらをすべて列挙する2つの付録が規格に追加される。

  • Core undefined behavior : コア言語の規定で「動作は未定義」と明示されているすべてのケース(80項目)
  • Ill-formed, no diagnostic required : 「診断は要求されない」とされているすべての規則(45項目)

各項目は、次の要素で構成される。

  • 安定した名前(例: expr.expr.eval=算術式の評価が未定義動作であるケース、basic.compound.invalid.pointer=無効なポインタ値の間接参照)
  • 本文の規定への相互参照(本文の該当箇所から付録の項目へもリンクされる)
  • 状況の要約
  • コード例

この付録は参考情報 (informative) であり、規範ではない。付録の追加によって言語の動作が変わることはなく、プログラムの正しさの調査や、処理系・検査ツールの網羅性の確認などに利用できるリファレンスを提供するものである。

また、各項目に与えられた安定した名前は、個々の未定義動作の緩和・削減を検討する取り組み(P3100など)において、問題を特定する共通の語彙としても使われる。

仕様

  • 未定義動作の付録が対象とするのは、コア言語の規定(字句規則からプリプロセッサまで)で「動作は未定義」などの言い回しによって明示的に規定されている未定義動作である
    • 規格が動作を定義していないことによる暗黙の未定義動作は対象外である
    • 標準ライブラリの規定による未定義動作も対象外である
  • IFNDRの付録が対象とするのは、「診断は要求されない」などの言い回しによって明示的に規定されている規則である
  • どちらの付録も、本文の章構成と対応する節に分けて項目を列挙する

未定義動作の一覧

分類 名前 説明
基本 intro.object.implicit.create 暗黙的なオブジェクト生成で、プログラムの動作が定義されることになるオブジェクトの集合が存在しない
基本 intro.object.implicit.pointer 暗黙的なオブジェクト生成後に、適切なオブジェクトへのポインタを生成できない
基本 basic.align.object.alignment 型のアライメント要求を満たさない記憶域にオブジェクトを生成する
基本 lifetime.outside.pointer.delete 生存期間外のオブジェクトへのポインタを、非トリビアルなデストラクタを持つクラス型に対するdelete式のオペランドに使用する
基本 lifetime.outside.pointer.member 生存期間外のオブジェクトへのポインタで、メンバ変数へアクセスするか非静的メンバ関数を呼び出す
基本 lifetime.outside.pointer.virtual 生存期間外のオブジェクトへのポインタを、仮想基底クラスへのポインタへ暗黙変換する
基本 lifetime.outside.pointer.dynamic.cast 生存期間外のオブジェクトへのポインタをdynamic_castのオペランドに使用する
基本 lifetime.outside.glvalue.access 生存期間外のオブジェクトを参照するglvalueで、そのオブジェクトへアクセスする
基本 lifetime.outside.glvalue.member 生存期間外のオブジェクトを参照するglvalueで、非静的メンバ関数を呼び出す
基本 lifetime.outside.glvalue.virtual 生存期間外のオブジェクトを参照するglvalueを、仮想基底クラスへの参照に束縛する
基本 lifetime.outside.glvalue.dynamic.cast 生存期間外のオブジェクトを参照するglvalueを、dynamic_castまたはtypeidのオペランドに使用する
基本 original.type.implicit.destructor 記憶域を占めるオブジェクトの型が、その記憶域の元の型と異なる状態で、非トリビアルな暗黙のデストラクタ呼び出しが行われる
基本 creating.within.const.complete.obj constな完全オブジェクトが占める(または生存期間終了前に占めていた)記憶域に、新しいオブジェクトを生成する
基本 basic.indet.value 評価の結果が不定値となる(エラー値の場合を除く)
基本 basic.stc.alloc.dealloc.constraint 確保関数・解放関数の動作が、規定された意味論的制約を満たさない
基本 basic.stc.alloc.zero.dereference サイズ0で確保関数を呼び出して返されたポインタを間接参照する
基本 basic.compound.invalid.pointer 解放済みの記憶域を参照するポインタ値を、間接参照するか解放関数へ渡す
基本 intro.execution.unsequenced.modification 同じメモリ位置への副作用が、同じメモリ位置への別の副作用や値の計算と順序付けられていない
基本 intro.races.data データ競合(一方が非アトミックで、互いに前後して発生しない、並行する競合アクセス)
基本 intro.progress.stops 終了していない実行スレッドが、実行ステップを進めなくなる
基本 basic.start.main.exit.during.destruction 静的・スレッド記憶域期間のオブジェクトの破棄中にstd::exit()を呼び出す
基本 basic.start.term.use.after.destruction 破棄済みの静的・スレッドローカルなブロックスコープオブジェクトを持つ関数を、静的・スレッド記憶域期間のオブジェクトの破棄中に呼び出す
expr.expr.eval 算術式の結果が数学的に定義されないか、その型で表現できない(符号付き整数のオーバーフローなど)
expr.basic.lvalue.strict.aliasing.violation オブジェクトを、その動的な型と類似しない型のglvalueを通してアクセスする(strict aliasing違反)
expr.basic.lvalue.union.initialization 共用体のdefault定義されたコピー・ムーブ操作を、生存期間内の類似した型のオブジェクトではない引数で呼び出す
expr.type.reference.lifetime 同じオブジェクトを指すポインタの同等の使用が無効となる状況で、参照を評価する
conv.lval.valid.representation 値表現がその型として妥当ではないオブジェクトを、lvalueからrvalueへ変換する
conv.double.out.of.range 浮動小数点数の値を、その値を表現できない浮動小数点型へ変換する
conv.fpint.float.not.represented 浮動小数点数から整数型への変換で、値が変換先の型で表現できない
conv.fpint.int.not.represented 整数型・スコープなし列挙型から浮動小数点型への変換で、値が変換先の型で表現できない
conv.ptr.virtual.base 生存期間内の有効なオブジェクトを指していない派生クラスへのポインタを、仮想基底クラスへのポインタへ変換する
conv.member.missing.member 基底クラスのメンバへのポインタを、そのメンバを含むことができない派生クラスのメンバへのポインタへ変換する
expr.call.different.type 関数を、その定義の関数型と呼び出し互換ではない関数型の式を通して呼び出す
expr.ref.member.not.similar クラスメンバアクセスE1.E2で、E1が参照するオブジェクトの型が式E1の型と類似しない
expr.dynamic.cast.pointer.lifetime 異なる型のオブジェクトや生存期間外のオブジェクトを指す非ヌルポインタに対して、dynamic_castを評価する
expr.dynamic.cast.glvalue.lifetime 類似しない型のオブジェクトや生存期間外のオブジェクトを参照するglvalueに対して、dynamic_castを評価する
expr.static.cast.base.class 基底クラス型のglvalueを、実際のオブジェクトが属さない派生クラスへの参照へstatic_castする
expr.static.cast.enum.outside.range 固定の基底型を持たない列挙型への変換で、元の値が列挙の値の範囲外である
expr.static.cast.fp.outside.range 浮動小数点数の明示的な変換で、値が変換先の型の範囲外である
expr.static.cast.downcast.wrong.derived.type その位置に派生クラスのオブジェクトが存在しないのに、基底クラスへのポインタを派生クラスへのポインタへstatic_castする
expr.static.cast.does.not.contain.original.member 元のメンバを含まないクラスのメンバへのポインタへstatic_castする
expr.unary.dereference オブジェクトも関数も指していないポインタを間接参照する
expr.new.non.allocating.null 非確保形式(placement形式)の確保関数がヌルポインタを返す
expr.delete.mismatch 単一オブジェクトのnew式の結果を、配列版のdelete[]で解放する
expr.delete.array.mismatch 配列のnew式の結果を、単一オブジェクト版のdeleteで解放する
expr.delete.dynamic.type.differ delete式で、削除するオブジェクトの静的な型が動的な型と異なり、基底クラスかつ仮想デストラクタを持つという要件を満たさない
expr.delete.dynamic.array.dynamic.type.differ 配列のdelete式で、削除するオブジェクトの動的な型が静的な型と異なる
expr.mptr.oper.not.contain.member メンバポインタ演算子E1.*E2で、E1の動的な型がE2が参照するメンバを含まない
expr.mptr.oper.member.func.null .*式の第2オペランドがヌルメンバポインタ値である
expr.mul.div.by.zero 0による除算・剰余
expr.mul.representable.type.result 除算・剰余で、商が結果の型で表現できない(INT_MIN / -1など)
expr.add.out.of.bounds 配列の範囲外を指すポインタを生成する
expr.add.sub.diff.pointers 同じ配列に属さないポインタ同士を減算する
expr.add.not.similar ポインタの加減算で、ポインタが指す型と配列要素の型が類似しない
expr.shift.neg.and.width 負の量、または型のビット幅以上の量によるシフト
expr.assign.overlap 代入で、転送元と転送先の記憶域が重なる
stmt.return.flow.off 戻り値の型がvoidではない関数(main()とコルーチンを除く)の末尾に到達する
stmt.return.coroutine.flow.off voidを返さないコルーチンの本体の末尾に到達する
stmt.dcl.local.static.init.recursive ブロックスコープの静的変数の初期化中に、その宣言へ再帰的に制御が入る
宣言 dcl.type.cv.modify.const.obj constオブジェクトを生存期間中に変更する
宣言 dcl.type.cv.access.volatile volatile修飾された型で定義されたオブジェクトへ、非volatileなglvalueを通してアクセスする
宣言 dcl.ref.incompatible.function 関数への参照を、参照の型と呼び出し互換ではない関数で初期化する
宣言 dcl.ref.incompatible.type オブジェクトへの参照を、参照の型を通してアクセスできない値で初期化する
宣言 dcl.ref.uninitialized.reference 初期化される前の参照を評価する
宣言 dcl.fct.def.coroutine.resume.not.suspended 中断していないコルーチンの再開メンバ関数を呼び出す
宣言 dcl.fct.def.coroutine.destroy.not.suspended 中断していないコルーチンのdestroy()を呼び出す
宣言 dcl.attr.assume.false assume属性の式が、その地点でtrueに評価されないことになる
宣言 dcl.attr.noreturn.eventually.returns noreturn属性付きで宣言された関数が返る
クラス class.dtor.no.longer.exists 生存期間が終了したオブジェクトに対して、デストラクタを呼び出す
クラス class.abstract.pure.virtual 抽象クラスのコンストラクタ・デストラクタから、純粋仮想関数を呼び出す
クラス class.base.init.mem.fun 基底クラスのすべてのメンバ初期化子が完了する前に、メンバ関数を呼び出す
クラス class.cdtor.before.ctor 非トリビアルなコンストラクタの実行開始前に、オブジェクトのメンバや基底クラスを参照する
クラス class.cdtor.after.dtor 非トリビアルなデストラクタの実行完了後に、オブジェクトのメンバや基底クラスを参照する
クラス class.cdtor.convert.pointer 構築開始前・破棄完了後のオブジェクトへのポインタを、基底クラスへのポインタへ変換する
クラス class.cdtor.form.pointer 構築開始前・破棄完了後のオブジェクトの、直接の非静的メンバへのポインタを作る
クラス class.cdtor.virtual.not.x 構築・破棄中の仮想関数呼び出しで、呼び出し対象のオブジェクトが構築・破棄中のオブジェクトやその部分オブジェクトではない
クラス class.cdtor.typeid typeidのオペランドが構築・破棄中のオブジェクトを参照し、オペランドの静的な型がそのクラスやその基底クラスではない
クラス class.cdtor.dynamic.cast dynamic_castのオペランドが構築・破棄中のオブジェクトを参照し、オペランドの静的な型がそのクラスやその基底クラスではない
テンプレート temp.inst.inf.recursion テンプレートのインスタンス化が無限に再帰する
例外 except.handle.handler.ctor.dtor コンストラクタ・デストラクタの関数tryブロックのハンドラで、そのオブジェクトのメンバや基底クラスを参照する

IFNDRの一覧

分類 名前 説明
字句 lex.name.reserved C++処理系のために予約された識別子を使用する
基本 basic.link.consistent.types 互いに到達可能ではない、同じ実体の異なる種類での複数の宣言がある
基本 basic.def.odr.minimum.one.def 破棄されない文からodr使用される関数・変数に定義がない
基本 basic.def.odr.injected.match ある翻訳単位でリフレクションの注入された宣言によって定義した実体が、別の翻訳単位の定義と要件を満たさない
基本 basic.def.odr.maximum.one.def 非インライン・非テンプレートの関数・変数が、異なる翻訳単位で複数回定義される
基本 basic.def.odr.definition.matches 異なる翻訳単位の定義同士が一致しない(ODR違反)
基本 basic.def.odr.unnamed.enum.same.type 同じスコープに、先頭の列挙子名が同じでリンケージ用のtypedef名を持たない複数の無名列挙型の定義がある
基本 basic.contract.vastart.contract.predicate 契約アサーションの述語の中でva_startを使用する
基本 basic.contract.handler.replacing.nonreplaceable 契約違反ハンドラを置き換えられない処理系で、置き換え関数となりうる関数を宣言する
基本 class.member.lookup.name.refers.diff.decl クラス内で使用した名前が、クラスの完成したスコープで再評価すると異なる宣言を参照する
expr.prim.req.always.sub.fail requires式の要件へのテンプレート引数の置換が、常に置換失敗となる
stmt.ambig.bound.diff.parse 構文解析中のテンプレートパラメータ内の名前が、試行的な構文解析時と異なって束縛される
宣言 dcl.constinit.specifier.not.reachable constinit指定子の有無が、互いに到達可能ではない宣言間で一致しない
宣言 dcl.inline.missing.on.definition 外部・モジュールリンケージを持つinline関数・変数で、定義域の終端から到達可能なinline宣言がない
宣言 dcl.fct.default.inline.same.defaults 複数の翻訳単位で定義されるインライン関数のデフォルト引数の集合が、翻訳単位の終端で異なる
宣言 dcl.contract.func.mismatched.contract.specifiers 互いに到達可能ではない関数の最初の宣言同士で、契約指定子が等価ではない
宣言 dcl.fct.def.replace.bad.replacement 置き換え可能関数の宣言が、要求される形(非インライン・C++言語リンケージ・戻り値の型など)を満たさない
宣言 dcl.link.mismatched.language.linkage 互いに到達可能ではない宣言同士で、言語リンケージが一致しない
宣言 dcl.align.diff.translation.units 実体のアライメント指定子が、翻訳単位間で異なる
宣言 dcl.attr.indet.mismatched.declarations 関数の最初の宣言同士で、パラメータのindeterminate属性の使用が一致しない
宣言 dcl.attr.noreturn.trans.unit.mismatch noreturn属性の有無が、翻訳単位間で一致しない
モジュール module.unit.reserved.identifiers モジュール名に、std+数字で始まる識別子や予約識別子を使用する
モジュール module.unit.named.module.no.partition 名前付きモジュールに、複数のプライマリモジュールインタフェース単位がある
モジュール module.unit.unexported.module.partition モジュールインタフェース単位であるパーティションが、プライマリモジュールインタフェース単位からエクスポートされていない
モジュール module.private.frag.other.module.units プライベートモジュールフラグメントを持つモジュールに、ほかのモジュール単位がある
クラス class.base.init.delegate.itself コンストラクタが、直接または間接に自分自身へ委譲する
クラス class.virtual.pure.or.defined 純粋ではない仮想関数に定義がない
オーバーロード over.literal.reserved 将来の標準化のために予約されたリテラルサフィックス識別子で、リテラル演算子を宣言する
テンプレート temp.pre.reach.def 暗黙的にインスタンス化されるテンプレートの定義が、すべての定義域の終端から到達可能ではなく、明示的インスタンス化もされていない
テンプレート temp.arg.template.sat.constraints テンプレートテンプレートパラメータによるインスタンス化で、到達可能であれば選択されたはずの部分特殊化が到達可能ではない
テンプレート temp.constr.atomic.equiv.but.not.equiv プログラムの妥当性・意味が、機能的に等価だが等価ではない2つのアトミック制約に依存する
テンプレート temp.constr.atomic.sat.result.diff 同一のアトミック制約とテンプレート引数に対する充足の結果が、プログラム中の異なる地点で異なる
テンプレート temp.constr.normal.invalid 制約の正規化中の置換が、無効な型や式を生成する
テンプレート temp.spec.partial.general.partial.reachable インスタンス化で使用されたはずの部分特殊化が、テンプレートの特殊化の使用箇所から到達可能ではない
テンプレート temp.over.link.equiv.not.equiv プログラムの妥当性・意味が、機能的に等価だが等価ではない2つの構成要素に依存する
テンプレート temp.res.general.default.but.not.found インスタンス化地点から到達可能だが名前探索では見つからない宣言のデフォルト引数・デフォルトテンプレート引数を考慮すると、プログラムの妥当性・意味が変わる
テンプレート temp.point.diff.pt.diff.meaning 2つの異なるインスタンス化地点が、ODRに従うとテンプレートの特殊化へ異なる意味を与える
テンプレート temp.dep.candidate.different.lookup.different 全翻訳単位の関連名前空間の外部リンケージを持つ全関数宣言を考慮すると、依存呼び出しが不適格になるかより良い一致が見つかる
テンプレート temp.explicit.decl.implicit.inst 明示的インスタンス化の宣言の対象であり暗黙的インスタンス化も起こる使い方をされている実体が、プログラム中のどこでも明示的インスタンス化の定義の対象になっていない
テンプレート temp.expl.spec.unreachable.declaration 暗黙的インスタンス化が起こる箇所から、一致したはずの明示的特殊化が到達可能ではない
テンプレート temp.expl.spec.missing.definition 明示的特殊化が宣言されているが、その定義が提供されていない
テンプレート temp.deduct.general.diff.order 同じ関数テンプレートの異なる宣言への置換で、テンプレートのインスタンス化が異なる順序で起こるか、起こらなくなる
プリプロセッサ cpp.cond.defined.after.macro マクロの展開がdefinedという前処理トークンを生成する
プリプロセッサ cpp.cond.defined.malformed defined単項演算子を、規定された文法の形に一致しない形で使用する
プリプロセッサ cpp.include.malformed.headername #include指令の後のトークン列からヘッダ名を構成できない

付録に列挙される未定義動作の例:

#include <climits>

int main()
{
  int n = INT_MAX;
  int m = n + 1;  // 未定義動作: 算術式の結果が表現可能な値の範囲にない
                  // 付録では expr.expr.eval という名前で列挙される

  int* p = nullptr;
  *p;             // 未定義動作: 無効なポインタ値の間接参照
                  // 付録では basic.compound.invalid.pointer という名前で列挙される
}

この機能が必要になった背景・経緯

C++のすべての未定義動作IFNDRを一覧化する作業は、以前から有用と認識されており、P1705R1による調査の試みや、その成果を規格自体の付録として正式化する提案(P2234R0・P3075R0)が行われてきた。この付録の文言は、C++ワーキングドラフトのブランチとして共同で開発され、C++29のドラフトへ取り込まれた。

未定義動作の網羅的な一覧は、安全性への関心の高まりを受けて未定義動作を分類し緩和策を検討するP3100のような提案の基礎資料にもなっており、規格自体が一覧を保守することで、こうした取り組みが参照できる安定した共通語彙が確立された。

関連項目

参照