yohhoyの日記

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

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

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

警告:1つの式内で同一変数を複数回更新する技巧的なコード記述は避けてください。こんな式を書くもんじゃねぇよ言語仕様書の紙束で張っ倒すぞ(#ノ゚Д゚)ㇸ📘 ...取り乱しました。

int i = 0; // Java,C#,C++,C
// let i = 0; // JavaScript

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

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

複合代入演算子+=の左辺変数i値を “どのタイミングで読み取るか” により結果が異なる。JavaJavaScriptC#ではi = i + (1 + ++i)相当と解釈され、いずれも値2が言語仕様により保証された結果である。*2

Java

i += 1 + ++iは下記順序で評価され、最終的に変数iは値2となる。

  1. 複合代入演算子+=左辺(変数i)の値0を保存しておく(★L)。
  2. 複合代入演算子+=右辺(1 + ++i)を評価し、値2をえる(★R)。
    1. 加算演算子+左辺(1)を評価し、値1をえる。
    2. 加算演算子+右辺(++i)を評価し、変数iの値を01に更新したのち、値1をえる。
    3. 部分式(1 + ++i)の評価結果として、1 + 1より値2をえる。
  3. 複合代入演算子+=の計算プロセスとして、手順1:保存した左辺の値(★L)と手順2:右辺の評価結果(★R)に対して加算操作(+)を適用し、値2をえる。
  4. 手順3の評価結果2を、複合代入演算子+=左辺(変数i)に代入する。

複合代入演算子の評価プロセスについて、Java Language Specification(JLS) Java SE 23 Edition, 15.26.2より一部引用(下線部は強調)。*3

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T) ((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.
(snip)
At run time, the expression is evaluated in one of two ways.

If the left-hand operand expression is not an array access expression, then:

  • First, the left-hand operand is evaluated to produce a variable. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason; the right-hand operand is not evaluated and no assignment occurs.
  • Otherwise, the value of the left-hand operand is saved and then the right-hand operand is evaluated. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason and no assignment occurs.
  • Otherwise, the saved value of the left-hand variable and the value of the right-hand operand are used to perform the binary operation indicated by the compound assignment operator. If this operation completes abruptly, then the assignment expression completes abruptly for the same reason and no assignment occurs.
  • Otherwise, the result of the binary operation is converted to the type of the left-hand variable, and the result of the conversion is stored into the variable.
JLS 23, 15.26.2 Compound Assignment Operators

JavaScript

i += 1 + ++iは下記順序で評価され、最終的に変数iは値2となる。

  1. 複合代入演算子+=左辺(変数i)の値0を保存しておく(lval)。
  2. 複合代入演算子+=右辺(1 + ++i)を評価し、値2をえる(rval)。
    1. 加算演算子+左辺(1)を評価し、値1をえる。
    2. 加算演算子+右辺(++i)を評価し、変数iの値を01に更新したのち、値1をえる。
    3. 部分式(1 + ++i)の評価結果として、1 + 1より値2をえる。
  3. 複合代入演算子+=の計算プロセスとして、保存した左辺の値(lval)と右辺の評価結果(rval)に対して加算操作(+)を適用し、値2をえる(r)。
  4. 手順3の評価結果2(r)を、複合代入演算子+=左辺(変数i; lref)に代入する。

複合代入演算子の評価プロセスについて、ECMAScript 2024 Language Specification 15th edition, 13.15.2より一部引用。*4

AssignmentExpression : LeftHandSideExpression AssignmentOperator AssignmentExpression

  1. Let lref be ? Evaluation of LeftHandSideExpression.
  2. Let lval be ? GetValue(lref).
  3. Let rref be ? Evaluation of AssignmentExpression.
  4. Let rval be ? GetValue(rref).
  5. Let assignmentOpText be the source text matched by AssignmentOperator.
  6. Let opText be the sequence of Unicode code points associated with assignmentOpText in the following table:
    • assignmentOpText += / opText +
    • (snip)
  7. Let r be ? ApplyStringOrNumericBinaryOperator(lval, opText, rval).
  8. Perform ? PutValue(lref, r).
  9. Return r.
ECMAScript 2024 Language Specification, 13.15.2 Runtime Semantics: Evaluation

C#

i += 1 + ++iは式i = i + (1 + ++i)として下記順序で評価され、最終的に変数iは値2となる。

  1. 複合代入演算子+=左辺(変数i)を評価し、値0をえる。
  2. 加算演算子+左辺(1)を評価し、値1をえる。
  3. 加算演算子+右辺(++i)を評価し、変数iの値を01に更新したのち、値1をえる。
  4. 手順1〜3評価結果から0 + (1 + 1)→値2を算出し、複合代入演算子+=左辺(変数i)に代入する。

演算子オペランドの評価順序および複合代入演算子の評価プロセスについて、ECMA-334 6th edition*5, 11.4.1, 11.18.2, 11.18.3より一部引用。

11.4 Operators
11.4.1 General
Expressions are constructed from operands and operators. The operators of an expression indicate which operations to apply to the operands.

(snip)

The order of evaluation of operators in an expression is determined by the precedence and associativity of the operators (§11.4.2).

Operands in an expression are evaluated from left to right.

Example: In F(i) + G(i++) * H(i), method F is called using the old value of i, then method G is called with the old value of i, and, finally, method H is called with the new value of i. This is separate from and unrelated to operator precedence. end example

11.18.2 Simple assignment
The run-time processing of a simple assignment of the form x = y consists of the following steps:

  • If x is classified as a variable:
    • x is evaluated to produce the variable.
    • y is evaluated and, if required, converted to the type of x through an implicit conversion (§10.2).
    • If the variable given by x is an array element of a reference_type, (snip)
    • The value resulting from the evaluation and conversion of y is stored into the location given by the evaluation of x.
  • (snip)

11.18.3 Compound assignment

An operation of the form x «op»= y is processed by applying binary operator overload resolution (§11.4.5) as if the operation was written x «op» y. Then,

  • If the return type of the selected operator is implicitly convertible to the type of x, the operation is evaluated as x = x «op» y, except that x is evaluated only once.
  • (snip)

The term "evaluated only once" means that in the evaluation of x «op» y, the results of any constituent expressions of x are temporarily saved and then reused when performing the assignment to x.

関連URL

*1:謝辞:本記事は https://twitter.com/YojoPaisen/status/1859465566324654494 および https://twitter.com/it_muitenai/status/1859163754644083193 経由で頂いた情報をベースにしています。

*2:JavaおよびJavaScript(ECMAScript)言語仕様の解釈には曖昧さが無い。一方でC#言語の仕様記述は曖昧さが残るため、本文中の仕様解釈に多少の疑念がないわけでもない。これ以外の解釈とする必然性もないし、たぶん合ってるんじゃない?

*3:JLS 同セクションの Example 15.26.2-2. Value Of Left-Hand Side Of Compound Assignment Is Saved Before Evaluation Of Right-Hand Side にて本記事との類似ケースが例示説明されている。

*4:ECMAScript言語仕様のRuntime Semantics記述に頻出する ? 記号は、仮想操作ReturnIfAbrupt適用を表す略記用プレフィクス(prefix)である。本記事においては例外を扱わないため、単に無視してもよい。詳細は§5.2.3.4 ReturnIfAbrupt Shorthands参照のこと。

*5:https://ecma-international.org/wp-content/uploads/ECMA-334_6th_edition_june_2022.pdf