異なるプログラミング言語における式i += 1 + ++i処理結果の違いについてメモ。*1
警告:1つの式内で同一変数を複数回更新する技巧的なコード記述は避けてください。こんな式を書くもんじゃねぇよ言語仕様書の紙束で張っ倒すぞ(#ノ゚Д゚)ㇸ📘 ...取り乱しました。
int i = 0; // Java,C#,C++,C // let i = 0; // JavaScript i += 1 + ++i; // 変数 i の値は?
まとめ:整数型の変数iが値0で初期化さているとき、下記プログラミング言語における処理結果は次の通り。
- Java:値
2 - JavaScript:値
2 - C#:値
2 - C:未定義動作(undefined behavior)(→id:yohhoy:20241125)
- C++(→id:yohhoy:20241125)
複合代入演算子+=の左辺変数i値を “どのタイミングで読み取るか” により結果が異なる。Java/JavaScript/C#ではi = i + (1 + ++i)相当と解釈され、いずれも値2が言語仕様により保証された結果である。*2
Java
式i += 1 + ++iは下記順序で評価され、最終的に変数iは値2となる。
- 複合代入演算子
+=左辺(変数i)の値0を保存しておく(★L)。 - 複合代入演算子
+=右辺(1 + ++i)を評価し、値2をえる(★R)。 - 複合代入演算子
+=の計算プロセスとして、手順1:保存した左辺の値(★L)と手順2:右辺の評価結果(★R)に対して加算操作(+)を適用し、値2をえる。 - 手順3の評価結果
2を、複合代入演算子+=左辺(変数i)に代入する。
複合代入演算子の評価プロセスについて、Java Language Specification(JLS) Java SE 23 Edition, 15.26.2より一部引用(下線部は強調)。*3
A compound assignment expression of the form
E1 op= E2is equivalent toE1 = (T) ((E1) op (E2)), whereTis the type ofE1, except thatE1is 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:
JLS 23, 15.26.2 Compound Assignment Operators
- 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.
JavaScript
式i += 1 + ++iは下記順序で評価され、最終的に変数iは値2となる。
- 複合代入演算子
+=左辺(変数i)の値0を保存しておく(lval)。 - 複合代入演算子
+=右辺(1 + ++i)を評価し、値2をえる(rval)。 - 複合代入演算子
+=の計算プロセスとして、保存した左辺の値(lval)と右辺の評価結果(rval)に対して加算操作(+)を適用し、値2をえる(r)。 - 手順3の評価結果
2(r)を、複合代入演算子+=左辺(変数i; lref)に代入する。
複合代入演算子の評価プロセスについて、ECMAScript 2024 Language Specification 15th edition, 13.15.2より一部引用。*4
AssignmentExpression : LeftHandSideExpression AssignmentOperator AssignmentExpression
ECMAScript 2024 Language Specification, 13.15.2 Runtime Semantics: Evaluation
- Let lref be
? Evaluationof LeftHandSideExpression.- Let lval be
? GetValue(lref).- Let rref be
? Evaluationof AssignmentExpression.- Let rval be
? GetValue(rref).- Let assignmentOpText be the source text matched by AssignmentOperator.
- Let opText be the sequence of Unicode code points associated with assignmentOpText in the following table:
- assignmentOpText
+=/ opText+- (snip)
- Let r be
? ApplyStringOrNumericBinaryOperator(lval, opText, rval).- Perform
? PutValue(lref, r).- Return
r.
C#
式i += 1 + ++iは式i = i + (1 + ++i)として下記順序で評価され、最終的に変数iは値2となる。
- 複合代入演算子
+=左辺(変数i)を評価し、値0をえる。 - 加算演算子
+左辺(1)を評価し、値1をえる。 - 加算演算子
+右辺(++i)を評価し、変数iの値を0→1に更新したのち、値1をえる。 - 手順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), methodFis called using the old value ofi, then methodGis called with the old value ofi, and, finally, methodHis called with the new value ofi. 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 formx = yconsists of the following steps:
- If
xis classified as a variable:
xis evaluated to produce the variable.yis evaluated and, if required, converted to the type ofxthrough an implicit conversion (§10.2).- If the variable given by
xis an array element of a reference_type, (snip)- The value resulting from the evaluation and conversion of
yis stored into the location given by the evaluation ofx.- (snip)
11.18.3 Compound assignment
An operation of the form
x «op»= yis processed by applying binary operator overload resolution (§11.4.5) as if the operation was writtenx «op» y. Then,
- If the return type of the selected operator is implicitly convertible to the type of
x, the operation is evaluated asx = x «op» y, except thatxis evaluated only once.- (snip)
The term "evaluated only once" means that in the evaluation of
x «op» y, the results of any constituent expressions ofxare temporarily saved and then reused when performing the assignment tox.
関連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