diff --git a/GLOBAL_DEFINED_WORDS.json b/GLOBAL_DEFINED_WORDS.json index c99b78f600..a80003570e 100644 --- a/GLOBAL_DEFINED_WORDS.json +++ b/GLOBAL_DEFINED_WORDS.json @@ -12,7 +12,7 @@ "未定義の動作": { "link": "/implementation-compliance.md#dfn-undefined-behavior", "yomi": "みていぎのどうさ", - "desc": "処理系は予期せぬ動作をする可能性がある。要するに動作保証対象外" + "desc": "処理系は予期せぬ動作をする可能性がある。要するに動作保証対象外。undefined behavior (UB)。" }, "未定義動作": { "redirect": "未定義の動作", "yomi": "みていぎどうさ" }, "動作は未定義": { "redirect": "未定義の動作", "yomi": "どうさはみていぎ" }, @@ -29,6 +29,12 @@ }, "未規定動作": { "redirect": "未規定の動作", "yomi": "みきていどうさ" }, "未規定": { "redirect": "未規定の動作", "yomi": "みきてい" }, + "エラー性の動作": { + "link": "/implementation-compliance.md#dfn-erroneous-behavior", + "yomi": "えらーせいのどうさ", + "desc": "未定義動作ではないが、誤ったプログラムの結果とされる動作。erroneous behavior (EB)。処理系によって診断や異常終了を実行することが許可されるが、処理が続行する場合もある" + }, + "エラー性動作": { "redirect": "エラー性の動作", "yomi": "えらーせいどうさ" }, "処理系定義の動作": { "link": "/implementation-compliance.md#dfn-implementation-defined-behavior", "yomi": "しょりけいていぎのどうさ", diff --git a/implementation-compliance.md b/implementation-compliance.md index 7ba64e97dc..e2e19ed36d 100644 --- a/implementation-compliance.md +++ b/implementation-compliance.md @@ -25,6 +25,7 @@ C++ の処理系は、翻訳を担うコンパイラと実行を担うオペレ - 未規定の動作 (unspecified behavior) とされた動作に対しては、処理系は考えられる動作の内の1つを行って良い。処理系は説明書にその動作を定義しなくて良い。 - 未定義の動作 (undefined behavior; 通称 UB) は、処理系が実際に行う動作について標準規格が如何なる要件もおかないことを表す。 - **文化圏固有動作** (locale-specific behavior) に対しては、処理系は現地の国家・文化・言語の風習に依存した動作を行う。処理系はその動作を説明書に記述する必要がある。 +- エラー性の動作 (erroneous behavior; 通称 EB) は、定義された動作であるが、処理系は診断情報を出力することが推奨される。その後に未規定の時点で、処理系は実行を終了しても良い。 これらの用語は処理系が取りうる動作の範囲を示すものであって、例えば "未定義の動作" という名前の具体的な動作がある訳ではないことに注意する。 diff --git a/implementation-status.md b/implementation-status.md index 417c7cf76a..f6414a20b1 100644 --- a/implementation-status.md +++ b/implementation-status.md @@ -305,7 +305,7 @@ | P0609R3: [構造化束縛への属性を許可](/lang/cpp26/attributes_for_structured_bindings.md) | `auto [a, b [[maybe_unused]], c] = f();`のように構造化束縛の要素に対して属性を付加できるようにする | 15 | 19 | | | | P3034R1: [モジュール宣言でのモジュール名のマクロ展開を禁止する](/lang/cpp26/module_declarations_shouldnt_be_macros.md.nolink) | `export module MACRO_NAME;`を禁止 | | | | | | P2809R3: [自明な無限ループは未定義動作ではないと規定](/lang/cpp26/trivial_infinite_loops_are_not_undefined_behavior.md.nolink) | 並行プログラムの進行保証などを考慮して無限ループを未定義動作ではないものとする | 14 | 19 | | | -| P2795R5: [未初期化変数の読み取りを不正動作 (erroneous behaviour: EB) とする](/lang/cpp26/erroneous_behaviour_for_uninitialized_reads.md.nolink) | 初期化されていない自動変数の読み取りの安全性を規定する | | | | | +| P2795R5: [未初期化変数の読み取りをエラー性動作とする](/lang/cpp26/erroneous_behavior_for_uninitialized_reads.md) | 初期化されていない自動変数の読み取りの安全性を規定する | | | | | | P2573R2: [関数宣言を削除する理由を指定できるようにする](/lang/cpp26/delete_reason.md) | `f() = delete("reason");` | 15 | 19 | | | | P2893R3: [可変引数テンプレートで`friend`宣言をできるようにする](/lang/cpp26/variadic_friends.md.nolink) | クラステンプレートの可変引数テンプレートでまとめて`friend`宣言できるようにする | 15 | | | | | P2747R2: [`constexpr`配置`new`](/lang/cpp26/constexpr_placement_new.md.nolink) | 定数式の文脈での配置`new`を許可 | | | | | diff --git a/lang/cpp26.md b/lang/cpp26.md index 3c74307342..39b9f861c6 100644 --- a/lang/cpp26.md +++ b/lang/cpp26.md @@ -17,7 +17,7 @@ C++26とは、2026年中に改訂される予定の、C++バージョンの通 | [不完全型へのポインタに対する`delete`を不適格とする](/lang/cpp26/deleting_a_pointer_to_an_incomplete_type_should_be_ill-formed.md.nolink) | 未定義動作を引き起こす操作をコンパイルエラーとする | | [返却された左辺値から暗黙変換された一時オブジェクトが参照に束縛されることを禁止する](/lang/cpp26/disallow_binding_a_returned_glvalue_to_a_temporary.md.nolink) | 寿命切れの変数によって引き起こされるバグを防止する | | [要素数不明の配列を集成体初期化する規則を明確化](/lang/cpp26/clarifying_rules_for_brace_elision_in_aggregate_initialization.md.nolink) | 配列要素の集成体初期化で`{}`が省略された場合の矛盾していた規定を修正 | -| [未初期化変数の読み取りを不正動作 (erroneous behaviour: EB) とする](/lang/cpp26/erroneous_behaviour_for_uninitialized_reads.md.nolink) | 初期化されていない自動変数の読み取りの安全性を規定する | +| [未初期化変数の読み取りをエラー性動作とする](/lang/cpp26/erroneous_behavior_for_uninitialized_reads.md) | 初期化されていない自動変数の読み取りの安全性を規定する | ### 文字列 diff --git a/lang/cpp26/erroneous_behavior_for_uninitialized_reads.md b/lang/cpp26/erroneous_behavior_for_uninitialized_reads.md new file mode 100644 index 0000000000..4cf7047e09 --- /dev/null +++ b/lang/cpp26/erroneous_behavior_for_uninitialized_reads.md @@ -0,0 +1,141 @@ +# 未初期化変数の読み取りをエラー性動作とする [P2795R5] +* cpp26[meta cpp] + + + +このページはC++26に採用される見込みの言語機能の変更を解説しています。 + +のちのC++規格でさらに変更される場合があるため[関連項目](#relative-page)を参照してください。 + + + +## 概要 +C++23までは、未初期化変数 (デフォルト初期化された変数) の読み取りは未定義動作として扱われていた。C++26では、この未定義動作による安全上のリスクを低減するため、「エラー性動作 (erroneous behavior; 通称 EB、別表記としてエラー性の動作)」を新設してその多くに割り当てることとした。 + +```cpp example +// C++23 +void f(int) {} + +int main() { + int x; // デフォルト初期化。xは不定値 (indeterminate value) をもつ + f(x); // 左辺値から右辺値への変換が未定義動作を引き起こす +} +``` + +未初期化の値は、コンパイラやターゲット環境によって定義された固定値である。コンパイラにはこの誤りを診断することが許可され、推奨されているが、誤りを無視して有効な読み取りとして扱うことも許可されている。このコードは誤りではあるが、実行ごとに異なる動作をしたり攻撃者に値を制御されたりといったリスクはなくなる。 + +エラー性動作は未定義動作(一切の保証が失われる)とはちがって、動作を定めたうえで診断も可能とするものである。 + +C++26では、不定値で初期化されることを明確に指示する`[[indeterminate]]`属性も導入され、以下のような動作となる: + +```cpp example +// C++26 +void f(int) {} + +int main() { + int x; // xはエラー性の値をもつ + int y [[indeterminate]]; // 意図して不定値に初期化されることを指示 + + f(x); // エラー性動作 (エラー性の値の読み取り) + f(y); // 未定義動作 (不定値の読み取り) +} +``` + + +## 仕様 +- 自動記憶域期間をもつオブジェクトの記憶域は確保時点で「エラー性の値 (erroneous value)」をもつとされ、処理系がプログラムの状態に依存せず決定する何らかの値で埋められる + - 動的記憶域期間であれば不定値、静的・スレッド記憶域期間であればゼロで埋められる。C++23までは自動記憶域期間も不定値で埋められていた +- 初期化されなかったスカラ型オブジェクトなど、値表現(パディングは含まない)内のいずれかのビットにエラー性の値をもつオブジェクトはエラー性の値をもつとされる +- 式が評価された結果としてエラー性の値が生成された場合、エラー性動作を引き起こす + - ただし、`unsigned char`(およびunsignedとなる場合は`char`)もしくは[`std::byte`](/reference/cstddef/byte.md)型のエラー性の値がこれらの型のオブジェクトの初期化・代入に使用される場合や値が破棄される場合はエラー性動作にならない + - これらのルールは、式が評価された結果として不定値が生成された場合に未定義の動作を引き起こすとする従来のルールと同様である +- エラー性動作を引き起こしたうえで生成された値は、後続の処理ではエラー性の値とはみなされない + +```cpp example +#include + +int g(bool b) { + unsigned char c; + unsigned char d = c; // エラー性動作ではない。dはエラー性の値をもつ + + assert(c == d); // 常に真、エラー性動作 (整数昇格) + + int e = d; // エラー性動作 (型変換) + return b ? d : 0; // bがtrueの場合にエラー性動作 +} + +int main() { + int d1, d2; + + int e1 = d1; // エラー性動作 + int e2 = d1; // エラー性動作 + + // 処理が続行した場合… + assert(e1 == e2); // 常に真、エラー性動作ではない。 + // エラー性動作の結果で生成された値 (e1とe2) は、 + // エラー性の値とはみなされない + assert(e1 == d1); // 常に真、エラー性動作 + assert(e2 == d1); // 常に真、エラー性動作 + + // エラー性動作ではないが + // d2はエラー性の値をもつ + std::memcpy(&d2, &d1, sizeof(int)); + + assert(e1 == d2); // 常に真、エラー性動作 + assert(e2 == d2); // 常に真、エラー性動作 +} +``` + +### `[[indeterminate]]`属性 +`[[indeterminate]]`属性は、自動変数が初期状態として意図して不定値をもつことを指示するものであり、自動変数の定義、もしくは関数のパラメータ宣言に適用できる。 + +関数のパラメータが`[[indeterminate]]`属性で宣言される場合、その関数の最初の宣言でそのように宣言されなければならない (注:関数宣言は複数行うことができるが、その最初の宣言で`[[indeterminate]]`属性をつけなければならない)。 + +`[[indeterminate]]`がつけられた変数から読み取りをした場合、未定義動作を引き起こす可能性がある。 + +```cpp example +struct T { + T() {} + int x; +}; + +int h(T t [[indeterminate]]) { + f(t.x); // 後述の関数呼び出しはここで未定義動作を引き起こす + return 0; +} + +int main() { + int _ = h(T()); +} +``` + +## 備考 +- 以下のようなケースでは、エラー性動作ではなく未定義動作を引き起こす可能性がある: + ```cpp + T* p; // 未初期化のポインタ。エラー性の値 (例としてヌルポインタ) をもつ + bool b; // 未初期化の真理値。 + // bool値として妥当な値表現をもたない可能性がある + // (例: 値表現が8bitで{0x00(false), 0x01(true)}の + // 2通りのみとする処理系で0xCCをもつなど) + + f(*p); // 間接参照は未定義動作を引き起こす + g(b); // bが妥当な値表現をもつ場合にエラー性動作、そうでなければ未定義動作 + ``` + +### 今後、エラー性動作に分類される可能性のある操作 + +現在、未定義動作に分類される以下の操作は、エラー性動作に分類できる可能性がある。 + +| 操作 | 備考 | +|------|------| +| 符号付き整数のオーバーフロー | 演算結果としてオーバーフローした場合に誤った結果になる可能性がある。これは珍しいバグではない。これは安全上の大きな問題ではない | +| 算術型の変換結果としてその型の表現可能な範囲を超えた | 符号付き整数のオーバーフローと同じ | +| 誤ったビットシフト (負のシフト幅や、上限を超えたシフト幅) | 符号付き整数のオーバーフローと同じ | +| ゼロ割り | いくつかの固定値での誤った結果となる可能性がある。影響が不明確であるため、変更にはコストがかかる | +| 戻り値型が非`void`な関数から返った、もしくは`[[noreturn]]`属性をつけた関数から返った | [`std::terminate()`](/reference/exception/terminate.md)が呼ばれる可能性がある。変更には軽いコストがかかるが、その変更にどの程度の価値があるかは不明 | +| 抽象クラスのコンストラクタ・デストラクタからの純粋仮想関数の呼び出し | 特定の純粋仮想ハンドラが呼ばれる可能性がある。実装によってはすでにエラー性動作のように扱われている可能性がある | +| 契約違反 | 契約に関する現在の策定作業では、契約違反時になにが起こるべきかという問題に直面している。エラー性動作という概念は有用な回答を与えてくれる可能性がある | + + +## 参照 +- [P2795R5 Erroneous behaviour for uninitialized reads](https://open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2795r5.html)