yohhoyの日記

技術的メモをしていきたい日記

i += 1 + ++i; の処理結果(C/C++編)

異なるプログラミング言語における式i += 1 + ++i処理結果の違いについてメモ。

警告:1つの式内で同一変数を複数回更新する技巧的なコード記述は避けてください。DO NOT WRITE THIS ON PRODUCTION CODE.

sequenced-before関係ルールを覚えてまで際どいコードを書くほど人生は長くないはず。

i = i++ + 1;の評価順規定 - yohhoyの日記
int i = 0; // Java,C#,C++,C
// let i = 0; // JavaScript

i += 1 + ++i;
// 変数 i の値は?

まとめ:整数型の変数iが値0で初期化さているとき、下記プログラミング言語における処理結果は次の通り。

複合代入演算子+=の左辺変数i値を “どのタイミングで読み取るか” により結果が異なる。C言語(C23現在)とC++14までは未定義動作(undefined behavior)となるが、C++17以降では順序規定の追加によって結果が保証されるようになる。こんなコードはダメ。ゼッタイ。

C90/99

式文末尾が副作用完了点(sequence point)と規定され、そこまでの間に複合代入(i += ...)および前置インクリメント(++i)により同一変数iに対する更新操作(modify)が複数回生じるため、未定義動作(undefined behavior)を引き起こす。C++98/03動作と同じ。

C99 6.5/p2, 6.5.16/p4, 6.8/p4より一部引用(下線部は強調)。

2 Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.

4 The order of evaluation of the operands is unspecified. If an attempt is made to modify the result of an assignment operator or to access it after the next sequence point, the behavior is undefined.

4 A full expression is an expression that is not part of another expression or of a declarator. Each of the following is a full expression: an initializer; the expression in an expression statement; (snip) The end of a full expression is a sequence point.

C11/17/23

複合代入演算子+=の左オペランド(i)における値の計算(value computation)と、右オペランドに含まれる前置インクリメント(++i)の副作用(side effect)が unsequenced 関係にあるため、式は未定義動作(undefined behavior)を引き起こす。C++11/14動作と同じ。

C23 6.5.1/p2-3、6.5.17.1/p3より引用(下線部は強調)。*1

2 If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.
3 The grouping of operators and operands is indicated by the syntax. Except as specified later, side effects and value computations of subexpressions are unsequenced.

3 An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment, but is not an lvalue. The type of an assignment expression is the type the left operand would have after lvalue conversion. The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced.

C++98/03

式文末尾が副作用完了点(sequence point)と規定され、そこまでの間に複合代入(i += ...)および前置インクリメント(++i)により同一変数iに対する更新操作(modify)が複数回生じるため、未定義動作(undefined behavior)を引き起こす。C90/99動作と同じ。

C++03+CWG351 1.9/p12, p16, 5/p4より引用(下線部は強調)。

12 A full-expression is an expression that is not a subexpression of another expression. If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.

16 There is a sequence point at the completion of evaluation of each full-expression.

4 Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified. Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined. [Example:

i = v[i++];       // the behavior is unspecified
i = 7, i++, i++;  // i becomes 9

i = ++i + 1;      // the behavior is undefined
i = i + 1;        // the value of i is incremented

-- end example]

C++11/14

複合代入演算子+=の左オペランド(i)における値の計算(value computation)と、右オペランドに含まれる前置インクリメント(++i)の副作用(side effect)が unsequenced 関係にあるため、式は未定義動作(undefined behavior)を引き起こす。C11/17/23動作と同じ。

C++14 1.9/p15, 5.17/p1より一部引用(下線部は強調)。

15 Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [Note: In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations. -- end note] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, and they are not potentially concurrent (1.10), the behavior is undefined. (snip)

1 The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. With respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation. (snip)

C++17/20/23

C++11/14の規定に加えて、複合代入演算子+=の右オペランド(1 + ++i)は左オペランド(i)より前に順序付けられる(sequenced before)ため、下記順序で評価が行われ変数iは値3となる。

  1. 複合代入演算子+=右辺(1 + ++i)を評価し、値2をえる。
    1. 加算演算子+左辺(1)の値を計算(value computation)し、値1をえる。手順1.2とは順不同。
    2. 加算演算子+右辺(++i)を評価し、値1をえる。手順1.1とは順不同。
      1. 前置インクリメント++オペランド(i)の値を計算(value computation)し、値0をえる。
      2. 前置インクリメントの値を計算(value computation)し、手順1.2.1評価結果から0 + 1→値1をえる。
      3. 前置インクリメントの副作用(side effect)として、変数iに値1を代入する。
    3. 加算の値の計算(value computation)として、手順1.1〜1.2評価結果から1 + 1→値2をえる。
  2. 複合代入演算子+=左辺(i)の値を計算(value computation)し、値1をえる。
  3. 複合代入の値の計算(value computation)として、手順1〜2評価結果から1 + 2→値3をえる。
  4. 複合代入の副作用(side effect)として、変数iに値3を代入する。

C++23 7.6.19/p1より一部引用(下線部は強調)。

1 The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue of the type of the left operand, referring to the left operand. The result in all cases is a bit-field if the left operand is a bit-field. In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. The right operand is sequenced before the left operand. With respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation. (snip)

関連URL

*1:C23 5.1.2.4/p2: "Evaluation of an expression in general includes both value computations and initiation of side effects."