C と Java における代入式の左辺と右辺の評価順序
#include <stdio.h> int main(int argc, char *argv[]) { int a[2] = { -1, -1 }; int i = 0; a[i] = ++i; printf("a[0]=%d\na[1]=%d\n", a[0], a[1]); return 0; }
こんな短いコードを gcc と cl でそれぞれコンパイルして実行すると、
gcc だと
a[0]=1 a[1]=-1
cl だと
a[0]=-1 a[1]=1
という出力が得られた。
gcc は左辺を評価してから右辺を評価するのに対し、cl だと右辺を評価してから左辺を評価するようだ。
これで経験的に C においては右辺と左辺の評価順序は不定である、ということがわかったんだけど、規格書の出典が見つからない。ご存知の方は教えてください。
なお、聞いてばかりだと申し訳ないので、同じロジックの以下コードを
public class eval_order { public static void main(String[] args) { int[] a = new int[] { -1, -1 }; int i = 0; a[i] = ++i; System.out.println("a[0]=" + a[0] + "\na[1]=" + a[1]); } }
Java で実行した場合は、gcc と同様に左辺から評価される。
a[0]=1 a[1]=-1
iconst_2 // スタックに 2 を積む newarray int // 配列を生成 dup // スタック破棄 iconst_0 // スタックに 0 を積む iconst_m1 // スタックに -1 を積む iastore // 配列要素 0 に -1 を格納 dup // スタック破棄 iconst_1 // スタックに 1 を積む iconst_m1 // スタックに -1 を積む iastore // 配列要素 1 に -1 を格納 astore_1 // 生成した配列をローカル変数 a に代入 iconst_0 // スタックに 0 を積む istore_2 // スタック上の値をローカル変数 i に代入 aload_1 // ローカル変数 a をスタックに積む iload_2 // ローカル変数 i をスタックに積む(インデックス) iinc 2, 1 // ローカル変数 i をインクリメント iload_2 // ローカル変数 i をスタックに積む(配列の値) iastore // ローカル変数 a に代入
配列、配列のインデックス、インクリメント、配列の値、という順番で処理が行われているので、このような結果になる。
この動作は Java 言語仕様によって定義されている。
First, the array reference subexpression of the left-hand operand array access expression is evaluated. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason; the index subexpression (of the left-hand operand array access expression) and the right-hand operand are not evaluated and no assignment occurs.
15.26.1 Simple Assignment Operator =
最初の一文で「まず、左辺オペランドの配列アクセス式の参照副式が評価される」と明言されている。Java の場合は、左から右へと評価が流れるという風に言語仕様で決まっているので、以下のような変態的なコードでも、比較的結果が予測しやすい。
int[] a = new int[]{ 1, 2, 3 }; int[] b = new int[]{ 4, 5, 6 }; int i = b[(b = a)[1]];
あなたは i がいくつになるか、お分かりだろうか?
正解は⇒ 6 (反転すると見えます)