Skip to content

C++26 : 「未初期化変数の読み取りをエラー性動作とする」を追加 #1349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
90c7ce3
C++26 : 「未初期化変数の読み取りを誤り起因動作とする」を追加
faithandbrave Sep 13, 2024
cd6d39b
C++26 未初期化変数の読み取り : erroneous behavior後の結果値に関する説明を整理
faithandbrave Sep 17, 2024
040c509
C++26 未初期化変数の読み取り : コード例にexampleをつけた
faithandbrave Sep 17, 2024
a114975
用語定義 : erroneous behaviorの概要を実態に合うよう修正
faithandbrave Sep 30, 2024
7f7759e
C++26 未初期化変数の読み取り : 概要を整理
faithandbrave Sep 30, 2024
6b0e3d7
C++26 未初期化変数の読み取り : assert結果を明記
faithandbrave Sep 30, 2024
2a4fe0e
C++26 未初期化変数の読み取り : 動的確保されたオブジェクトが対象外であることを記載
faithandbrave Sep 30, 2024
30cfb4e
erroneous behavior/valueを一旦訳さない
faithandbrave Oct 7, 2024
7d2188d
C++26 未初期化値の読み取り : 未定義動作になりえるケースを記載
faithandbrave Oct 7, 2024
c965e26
用語 : 読みを修正
faithandbrave Oct 8, 2024
7e68271
C++26 未初期化変数の読み取り : 訳してない用語のマーキング
faithandbrave Oct 8, 2024
aeee953
C++26 未初期化変数の読み取り : 仕様説明を見直し
faithandbrave Oct 8, 2024
5e8c37d
C++26 未初期化変数の読み取り : indeterminateのサンプルコードミスを修正
faithandbrave Oct 8, 2024
afee388
C++26 未初期化変数の読み取り : 抜けていた「常に真」を追加
faithandbrave Oct 8, 2024
94e04bf
C++26 未初期化変数の読み取り : boolの例示を見直し
faithandbrave Oct 8, 2024
a81c832
C++26 未初期化変数の読み取り : 「明確に定義された動作」を削除
faithandbrave Oct 10, 2024
616c00b
C++26 未初期化変数の読み取り : 今後の話を仕様から備考に移動
faithandbrave Oct 10, 2024
82d6360
C++26 未初期化変数の読み取り : 決定した訳語を適用
faithandbrave Nov 7, 2024
6010481
GLOBAL_DEFINED_WORDS: EB に倣い UB も英名を表記
akinomyoga Nov 7, 2024
28eb999
標準規格と処理系: エラー性の動作を言及
akinomyoga Nov 7, 2024
1c11eb8
fix typo
faithandbrave Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions GLOBAL_DEFINED_WORDS.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
},
"未規定動作": { "redirect": "未規定の動作", "yomi": "みきていどうさ" },
"未規定": { "redirect": "未規定の動作", "yomi": "みきてい" },
"「erroneous behavior」": {
"yomi": "えろーにあすびへいびあ",
"desc": "未定義動作ではないが、誤ったプログラムの結果とされる動作。erroneous behavior (EB)。処理系によって診断や異常終了を実行することが許可されるが、処理が続行する場合もある"
},
"処理系定義の動作": {
"link": "/implementation-compliance.md#dfn-implementation-defined-behavior",
"yomi": "しょりけいていぎのどうさ",
Expand Down
2 changes: 1 addition & 1 deletion implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: [未初期化変数の読み取りを"erroneous behavior"とする](/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`を許可 | | | | |
Expand Down
2 changes: 1 addition & 1 deletion lang/cpp26.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) | 初期化されていない自動変数の読み取りの安全性を規定する |
| [未初期化変数の読み取りを「erroneous behavior」とする](/lang/cpp26/erroneous_behavior_for_uninitialized_reads.md) | 初期化されていない自動変数の読み取りの安全性を規定する |


### 文字列
Expand Down
142 changes: 142 additions & 0 deletions lang/cpp26/erroneous_behavior_for_uninitialized_reads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# 未初期化変数の読み取りを「erroneous behavior」とする [P2795R5]
* cpp26[meta cpp]

<!-- start lang caution -->

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

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

<!-- last lang caution -->

## 概要
C++23までは、未初期化変数 (デフォルト初期化された変数) の読み取りは未定義動作として扱われていた。C++26では、この未定義動作による安全上のリスクを低減するため、「「erroneous behavior」 (erroneous behavior; 通称 EB)」を新設してその多くに割り当てることとした。

```cpp example
// C++23
void f(int) {}

int main() {
int x; // デフォルト初期化。xは不定値 (indeterminate value) をもつ
f(x); // 左辺値から右辺値への変換が未定義動作を引き起こす
}
```

未初期化の値は、コンパイラやターゲット環境によって定義された固定値である。コンパイラにはこの誤りを診断することが許可され、推奨されているが、誤りを無視して有効な読み取りとして扱うことも許可されている。このコードは誤りではあるが、実行ごとに異なる動作をしたり攻撃者に値を制御されたりといったリスクはなくなる。

「erroneous behavior」は未定義動作とはちがって、明確に定義された動作をするがコードとして正しくない、ということを規定するものであり、処理系に対して有用な診断を提供するものである。

C++26では、不定値で初期化されることを明確に指示する`[[indeterminate]]`属性も導入され、以下のような動作となる:

```cpp example
// C++26
void f(int) {}

int main() {
int x; // xは「erroneous value」をもつ
int y [[indeterminate]]; // 意図して不定値に初期化されることを指示

f(x); // 「erroneous behavior」 (「erroneous value」の読み取り)
f(y); // 未定義動作 (不定値の読み取り)
}
```


## 仕様
- 自動記憶域期間をもつオブジェクトの記憶域は確保時点で「erroneous value」をもつとされ、処理系がプログラムの状態に依存せず決定する何らかの値で埋められる
- 動的記憶域期間であれば不定値、静的・スレッド記憶域期間であればゼロで埋められる。C++23までは自動記憶域期間も不定値で埋められていた
- 初期化されなかったスカラ型オブジェクトなど、値表現(パディングは含まない)内のいずれかのビットに「erroneous value」をもつオブジェクトは「erroneous value」をもつとされる
- 式が評価された結果として「erroneous value」が生成された場合、「erroneous behavior」を引き起こす
- ただし、`unsigned char`(およびunsignedとなる場合は`char`)もしくは[`std::byte`](/reference/cstddef/byte.md)型の「erroneous value」がこれらの型のオブジェクトの初期化・代入に使用される場合や値が破棄される場合は「erroneous behavior」にならない
- これらのルールは、式が評価された結果として不定値が生成された場合に未定義の動作を引き起こすとする従来のルールと同様である
- 「erroneous behavior」を引き起こしたうえで生成された値は、後続の処理では「erroneous value」とはみなされない

```cpp example
#include <cassert>

int g(bool b) {
unsigned char c;
unsigned char d = c; // 「erroneous behavior」ではない。dは「erroneous value」をもつ

assert(c == d); // 常に真、「erroneous behavior」 (整数昇格)

int e = d; // 「erroneous behavior」 (型変換)
return b ? d : 0; // bがtrueの場合に「erroneous behavior」
}

int main() {
int d1, d2;

int e1 = d1; // 「erroneous behavior」
int e2 = d1; // 「erroneous behavior」

// 処理が続行した場合…
assert(e1 == e2); // 常に真、「erroneous behavior」ではない。
// 「erroneous behavior」の結果で生成された値 (e1とe2) は、
// 「erroneous value」とはみなされない
assert(e1 == d1); // 常に真、「erroneous behavior」
assert(e2 == d1); // 常に真、「erroneous behavior」

// 「erroneous behavior」ではないが
// d2は「erroneous value」をもつ
std::memcpy(&d2, &d1, sizeof(int));

assert(e1 == d2); // 常に真、「erroneous behavior」
assert(e2 == d2); // 常に真、「erroneous behavior」
}
```

### `[[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());
}
```

### 今後、「erroneous behavior」に分類される可能性のある操作

現在、未定義動作に分類される以下の操作は、「erroneous behavior」に分類できる可能性がある。

| 操作 | 備考 |
|------|------|
| 符号付き整数のオーバーフロー | 演算結果としてオーバーフローした場合に誤った結果になる可能性がある。これは珍しいバグではない。これは安全上の大きな問題ではない |
| 算術型の変換結果としてその型の表現可能な範囲を超えた | 符号付き整数のオーバーフローと同じ |
| 誤ったビットシフト (負のシフト幅や、上限を超えたシフト幅) | 符号付き整数のオーバーフローと同じ |
| ゼロ割り | いくつかの固定値での誤った結果となる可能性がある。影響が不明確であるため、変更にはコストがかかる |
| 戻り値型が非`void`な関数から返った、もしくは`[[noreturn]]`属性をつけた関数から返った | [`std::terminate()`](/reference/exception/terminate.md)が呼ばれる可能性がある。変更には軽いコストがかかるが、その変更にどの程度の価値があるかは不明 |
| 抽象クラスのコンストラクタ・デストラクタからの純粋仮想関数の呼び出し | 特定の純粋仮想ハンドラが呼ばれる可能性がある。実装によってはすでに「erroneous behavior」のように扱われている可能性がある |
| 契約違反 | 契約に関する現在の策定作業では、契約違反時になにが起こるべきかという問題に直面している。「erroneous behavior」という概念は有用な回答を与えてくれる可能性がある |


## 備考
- 以下のようなケースでは、erroneous behaviorではなく未定義動作を引き起こす可能性がある:
```cpp
T* p; // 未初期化のポインタ。erroneous value (例としてヌルポインタ) をもつ
bool b; // 未初期化の真理値。
// bool値として妥当な値表現をもたない可能性がある
// (例: 値表現が8bitで{0x00(false), 0x01(true)}の
// 2通りのみとする処理系で0xCCをもつなど)

f(*p); // 間接参照は未定義動作を引き起こす
g(b); // bが妥当な値表現をもつ場合にerroneous behavior、そうでなければ未定義動作
```


## 参照
- [P2795R5 Erroneous behaviour for uninitialized reads](https://open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2795r5.html)
Loading