インクリメント in ローカル変数、インスタンス変数、クラス変数の違い

id:terazzoさんのブコメのおかげで、ひとまず、前置/後置インクリメント命令を定数化した箇所を見つけました。ありがとうございます。

{OpenJDK6_dir}/langtools/src/share/classes/com/sun/tools/javac/tree以下のJCTree.java(268〜275行目付近)にありました。

が、構文木の詳細はまだ追えていません。この週末はだらけていて、気づいたらこんなカンジでした。

今日は、同じインクリメントに関するテーマですが、別の話です。

Java仮想マシン仕様』の演算子に関する解説を眺めていたら、気になることが書かれていました。

3.11.3「算術命令」に、次の記述があります。

  • ローカル変数のインクリメント:iinc

問. ローカル変数以外、つまり、クラスのフィールドの値をインクリメントした場合には、iinc命令は使われないのか?

答. 使われない。iaddが使われる。

(追記)ただし、フィールドをインクリメントしたときにiaddが使われるのは、あくまでもOpenJDKのコンパイラの実装であり、iincを使ったJDK実装も可能なのだそうです。id:m11m さんからコメントで指摘を頂きました。ありがとうございます。

分からなかったので確認してみたら、そんな結果でした。へー。(でっていう・・・)

確認過程をメモしておきます。

サンプルコード

ローカル変数のインクリメント
package study.increment;

public class PreIncrement {
	public static void main(String[] args) {
		int hoge = 10;
		++hoge;
	}
}
インスタンス変数のインクリメント

「フィールドをインクリメントしたらどの命令が使用されるか」について、staticと非staticなフィールドをそれぞれ確認します。

インクリメントされる値を定義したクラスです。

Data.java

package study.increment;

public class Data {
	int hoge;
}

インスタンス変数をインクリメントするコードです。Dataクラスのオブジェクトをインスタンス化して、変数を直接インクリメントします。

FieldPreIncrement.java

package study.increment;

public class FieldPreIncrement {
	public static void main(String[] args) {
		Data data = new Data();
		data.hoge = 10;
		data.hoge++;
	}
}
クラス変数のインクリメント

インクリメントされる値を定義したクラスです。

SData.java

package study.increment;

public class SData {
	static int hoge;
}

クラス変数をインクリメントするコードです。

StaticFieldPreIncrement.java

package study.increment;

public class StaticFieldPreIncrement {
	public static void main(String[] args) {
		SData.hoge = 10;
		SData.hoge++;
	}
}

javap結果

今回は最初からjavapのお世話になります。

バイトコード命令を見たいときにjadっても(当然ながら)無意味だと前回学んだので・・・。

ローカル変数をインクリメントするコードのjavap出力結果

iincが使われています。

>javap -c PreIncrement
Compiled from "PreIncrement.java"
public class study.increment.PreIncrement extends java.lang.Object{
public study.increment.PreIncrement();
  Code:
   0:   aload_0
   1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   bipush  10
   2:   istore_1
   3:   iinc    1, 1
   6:   return

}
インスタンス変数をインクリメントするコードのjavap出力結果

FieldPreIncrementには前置インクリメントを記述しましたが、「20:」の行でiaddになっています。iincはありません。

>javap -c FieldPreIncrement
Compiled from "FieldPreIncrement.java"
public class study.increment.FieldPreIncrement extends java.lang.Object{
public study.increment.FieldPreIncrement();
  Code:
   0:   aload_0
   1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   new     #16; //class study/increment/Data
   3:   dup
   4:   invokespecial   #18; //Method study/increment/Data."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   bipush  10
   11:  putfield        #19; //Field study/increment/Data.hoge:I
   14:  aload_1
   15:  dup
   16:  getfield        #19; //Field study/increment/Data.hoge:I
   19:  iconst_1
   20:  iadd
   21:  putfield        #19; //Field study/increment/Data.hoge:I
   24:  return

}

クラス変数をインクリメントするコードのjavap出力結果

こちらも、インスタンス変数のときと同様、iaddが使われています。

>javap -c StaticFieldPreIncrement
Compiled from "StaticFieldPreIncrement.java"
public class study.increment.StaticFieldPreIncrement extends java.lang.Object{
public study.increment.StaticFieldPreIncrement();
  Code:
   0:   aload_0
   1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   bipush  10
   2:   putstatic       #16; //Field study/increment/SData.hoge:I
   5:   getstatic       #16; //Field study/increment/SData.hoge:I
   8:   iconst_1
   9:   iadd
   10:  putstatic       #16; //Field study/increment/SData.hoge:I
   13:  return

}

メモ

プログラムコードにはインクリメントを書いたのに、内部で加算に置き換わっているなんて知りませんでした。インクリメントの命令(iinc)が存在するのだから、「++」と書けば必ずそれが使われるのだと誤解していました。

そもそも、ローカル変数やインスタンス変数(非static)の値と、クラス変数(static変数)の値は、実行時にオブジェクトを持つか持たないかという大きな違いがあります。自分にとってはその区分が印象強かったので、発行される命令の区分が違うところにあったのは意外でした。

メモリモデルを正しく理解していれば、これもあっさり腑に落ちる話なのでしょうか。追々勉強します。

iincはint型でしかサポートされない孤独なJVM命令…わたしに勇気を与えてくれる。