次の C プログラムを考えてみましょう。
int i = 0;
int post_increment_i() { return i++; }
int main() {
i = post_increment_i();
return i;
}
C 標準の 2011 年版 (C11 として知られる) に関して、次の代替案のうち正しいものはどれですか。
- C11 は、main が 0 を返すことを保証します。
- C11 は、main が 0 または 1 を返すことを保証します。
- このプログラムの動作は、C11 に従って未定義です。
C11 標準からの関連スニペット:
5.1.2.3 プログラムの実行
揮発性オブジェクトへのアクセス、オブジェクトの変更、ファイルの変更、またはこれらの操作のいずれかを行う関数の呼び出しはすべて、実行環境の状態の変化である副作用です。一般に、式の評価には、値の計算と副作用の開始の両方が含まれます。左辺値式の値の計算には、指定されたオブジェクトの ID の決定が含まれます。
前に順序付けされるのは、単一のスレッドによって実行される評価間の非対称で推移的なペアワイズ関係であり、これらの評価間に部分的な順序が生じます。任意の 2 つの評価 A と B が与えられた場合、A が B の前に配列されている場合、A の実行は B の実行よりも前に実行されます (逆に、A が B の前に配列されている場合、B は A の後に配列されます)。 B の前または後に、A と B は順序付けされていません。評価 A と B は、A が B の前または後にシーケンスされる場合、不定にシーケンスされますが、どちらが指定されていません。13式 A と B の評価の間にシーケンス ポイントが存在することは、A に関連付けられたすべての値の計算と副作用が、B に関連付けられたすべての値の計算と副作用の前に並べられることを意味します。 .)
13) 順序付けられていない評価の実行はインターリーブできます。不確定な順序の評価はインターリーブできませんが、任意の順序で実行できます。
6.5 式
式は、値の計算を指定するか、オブジェクトまたは関数を指定するか、副作用を生成するか、またはそれらの組み合わせを実行する一連の演算子とオペランドです。演算子のオペランドの値の計算は、演算子の結果の値の計算の前に並べられます。
スカラー オブジェクトに対する副作用が、同じスカラー オブジェクトに対する別の副作用または同じスカラー オブジェクトの値を使用した値の計算と比較してシーケンス化されていない場合、動作は未定義です。式の部分式に複数の許容される順序付けがある場合、順序付けのいずれかでそのような順序付けされていない副作用が発生した場合、動作は未定義です。
6.5.2.2 関数呼び出し
関数指定子と実際の引数の評価の後、実際の呼び出しの前に、シーケンス ポイントがあります。呼び出された関数の本体の実行の前後に特に順序付けされていない呼び出し側関数 (他の関数呼び出しを含む) 内のすべての評価は、呼び出された関数の実行に関して不定に順序付けられます。94
94) 言い換えれば、関数の実行は互いに「インターリーブ」しません。
6.5.2.4 後置インクリメントおよびデクリメント演算子
後置 ++ 演算子の結果は、オペランドの値です。副作用として、オペランド オブジェクトの値がインクリメントされます (つまり、適切な型の値 1 がそれに追加されます)。[...]結果の値の計算は、オペランドの格納された値を更新する副作用の前に順序付けられます。不定順序の関数呼び出しに関しては、後置 ++ の操作は単一の評価です。
6.5.16 割り当て
代入演算子は、左オペランドで指定されたオブジェクトに値を格納します。[...] 左オペランドの格納された値を更新する副作用は、左オペランドと右オペランドの値の計算の後に順序付けられます。オペランドの評価は順不同です。
6.8 ステートメントとブロック
完全な式は、別の式または宣言子の一部ではない式です。次のそれぞれは完全な式です: [...] 式ステートメント内の式。[...] return ステートメントの (オプションの) 式。完全な式の評価と、次に評価される完全な式の評価の間には、シーケンス ポイントがあります。
上記の 3 つの選択肢は、それぞれ次の 3 つのケースに対応します。
- 後置インクリメント演算子の副作用は、メインの割り当ての前に並べられます。
- 後置インクリメント演算子の副作用は、メインの代入の前または後のいずれかに配列され、C11 はどちらを指定しません。(つまり、2 つの副作用の順序は不定です。)
- 2 つの副作用は順序付けられていません。
次の一連の推論により、最初の選択肢が成り立つようです。
呼び出された関数の本体の実行の前または後に特に順序付けされていない呼び出し側関数 (他の関数呼び出しを含む) 内のすべての評価は、呼び出された関数の実行に関して不定に順序付けられるという規則を考慮してください。6.5.2.2。仮定 A: main の代入演算子の副作用は、このような「評価」です。仮定 B: 「呼び出された関数の実行」という句には、後置インクリメント演算子の値計算と後置インクリメント演算子の副作用の両方が含まれます。これらの仮定と上記のルールから、I) 値の計算と後置インクリメント演算子の副作用の両方がメインの代入演算子の副作用の前に並べられるか、II) 値の計算と副作用後置インクリメント演算子の両方が、メインの代入演算子の副作用の後に並べられます。
ルールを考慮してください。左側のオペランドの格納された値を更新する副作用は、左側と右側のオペランドの値の計算の後に順序付けられます。このルールは、上記のケース I を除外します。したがって、ケース II が成立します。QED
全体として、これはかなり強力な議論のように見えます。また、これは、直感的に考えられる最も可能性の高い代替案に対応しています。
ただし、「評価」および「呼び出された関数の実行」という用語の特定の解釈 (仮定 A および B) と、完全に単純ではない推論に依存しているので、人々がこの解釈が間違っていると信じる理由。脚注 94 は、呼び出し元が呼び出し先とインターリーブしないという意味でも適用される場合にのみ、この解釈と同等であることに注意してください。これは、「インターリーブ」が「abab」の意味でのインターリーブを意味することを意味します。弱い「アバ」の意味での呼び出し先。また、i = i++