コンパイラの解析 (3) クラスとインスタンスの初期化
Table of Contents シンボル名 クラスの初期化 インスタンスの生成
シンボル名 JavaやC++のシンボルは名前マングルがかかった状態でオブジェクトファイルに格納される C言語のシンボルよりも多くの情報を含む 名前空間 引数情報
extern “C” extern “C” の記述は、マングルの抑制 extern “C” をしないとマングルされる extern "C" void sample_c(int a) {} void sample_cxx(int a) {} $ objdump -t mangle.o | grep sample 00000000 g F .text 00000005 sample_c 00000006 g F .text 00000005 _Z10sample_cxxi
名前デマングルツール binutilsのc++filtコマンドでデマングルできる 慣れれば脳内にフィルタを作れる? $ objdump -t mangle.o | grep sample | c++filt 00000000 g F .text 00000005 sample_c 00000006 g F .text 00000005 sample_cxx(int)
名前マングルの略式法則 フィールド メソッド <>一つ分で一つのシンボル/基本型を表す _Z<フィールド名>
名前マングル – シンボル 通常のシンボルは次の形式でマングル 例 <名前文字数><名前> a -> 1a sample -> 6sample
名前マングル – 名前空間つきシンボル 名前空間つきのシンボル 例 N<シンボル1><シンボル2>…E a::b -> N1a1bE java::lang::Math -> N4java4lang4MathE
名前マングル – 基本型 基本型は1文字にマングルされる メソッドのマングル時、引数に利用 int -> i double -> d void -> v … メソッドのマングル時、引数に利用 foo() は foo(void) と常に解釈
名前マングル – ポインタ ポインタは次の形式でマングル 例 P<シンボル> int * -> Pi int ** -> Pii java::lang::Object* -> PN4java4lang6ObjectE
名前マングル – テンプレート テンプレートは次の形式 例 I<シンボル1><シンボル2>…E JArray<int> -> 6JArrayIiE JArray<Hoge> -> 6JArrayIN4HogeEE
名前マングル – ここまでの例 フィールド メソッド java::lang::Object::class$ -> _ZN4java4lang6Object6class$E java::lang::Math::PI -> _ZN4java4lang4Math2PIE メソッド hoge::foo(int, java::lang::Object*) -> _ZN4hoge3fooEiPN4java4lang6ObjectE Sample::main(JArray<java::lang::String *>*) -> _ZN6Sample4mainEP6JArrayIPN4java4lang6StringEE
名前マングル – ここまでの例 (分解) Sample::main(JArray<java::lang::String *>*) _Z N 6Sample 4main E P 6JArray I P N 4java 4lang 6String E E
名前マングル – 省略形 同じ名前にでてくるシンボルは再利用できる hoge::Foo::b(int, hoge::Bar) S<数字>_ なし, 0, 1, … の順番で先頭から数字がつき、再利用可能 hoge::Foo::b(int, hoge::Bar) -> _ZN4hoge3Foo1bEiNS_3BarE hoge::Foo::a(int, hoge::Foo) -> _ZN4hoge3Foo1aEiS0_ foo::f(java::lang::Object*,java::lang::Object*) _ZN3foo1fEPN4java4lang6ObjectES3_
クラスの初期化 (1) ここの部分はどうコンパイルされる? 変数の初期化用コード public class StaticInit { public static double SQRT2 = Math.sqrt(2); }
クラスの初期化 (2) 逆アセンブルしてみる javap –c <class> $ javap -c StaticInit Compiled from "StaticInit.java“ .. static {}; Code: 0: ldc2_w #2; //double 2.0d 3: invokestatic #4; //Method java/lang/Math.sqrt:(D)D 6: putstatic #5; //Field SQRT2:D 9: return
クラス初期化子 (1) Static initializer (クラス初期化子) public class ClassInit { static { System.out.println("clinit"); } public static void main(String[] args) { System.out.println("main"); } }
クラス初期化子 (2) 実行すると、mainメソッドの前に呼び出される $ java ClassInit clinit main
クラス初期化子 (3) 誰がクラスの初期化を行うのか? mainが行っている形跡はない $ javap -c ClassInit Compiled from "ClassInit.java“ .. public static void main(java.lang.String[]); Code: 0: getstatic #2; // System.out 3: ldc #3; // "main“ 5: invokevirtual #4; // PrintStream.println(String) 8: return
クラス初期化のルール Java Virtual Machine Specification 2nd →Mainメソッドを呼び出す直前に初期化 次の場合、クラスTはVMによって初期化される Tの子クラスを初期化する直前 Tのインスタンスを生成する直前 Tのクラスメソッドを起動する直前 Tの定数でないクラス変数を参照する直前 同じクラスは1度しか初期化されない →Mainメソッドを呼び出す直前に初期化
一息 (1) Subを実行した結果は? class Circu { static final String S1 = Sub.S2; } class Sub extends Circu { static final String S2 = new String("Sub"); public static void main(String[] args) { System.out.println(Circu.S1); } }
一息 (2) トレースしてみる クラスメソッドの起動 →Subの初期化 class Circu { static final String S1 = Sub.S2; } class Sub extends Circu { static final String S2 = new String("Sub"); public static void main(String[] args) { System.out.println(Circu.S1); } } 子クラスの初期化 →Circuの初期化 S2は非定数 →初期化までnull S2は未初期化 →S1 == null
一息 (3) “null”と表示される Circu.S1初期化の時点でSub.S2が未初期化 $ java Sub null
閑話休題 gcjでは明示的にクラスの初期化をする gcjではコンパイル済みコードを実行 クラスの初期化方法を解析
GCJにおけるクラスの初期化 (1) 他のクラスを初期化するコードをコンパイル クラスメソッドの呼び出しはクラス初期化の原因 java.lang.Mathを初期化する public class GcjClinit { public static double sqrt(double d) { return Math.sqrt(d); }
GCJにおけるクラスの初期化 (2) コンパイルしたものを分析 $ gcj -S GcjClinit.java _ZN9GcjClinit4sqrtEd: .. (prologue) pushl $_ZN9GcjClinit6class$E call _Jv_InitClass .. call _ZN4java4lang4Math4sqrtEd .. (epilogue)
GCJにおけるクラスの初期化 (3) _Jv_InitClassとその引数について分析 $ echo '_ZN9GcjClinit6class$E' | c++filt GcjClinit::class$ $ objdump -T /usr/lib/libgcj.so.5 | grep _Jv_InitClass 06cc8632 w DF .text 0000002b Base _Jv_InitClass
GCJにおけるクラスの初期化 (4) CNIからクラスの初期化を行う実験 下記のクラスをCNIから初期化する public class ClassInit { static { System.out.println("clinit"); } public static void main(String[] args) { System.out.println("main"); } }
GCJにおけるクラスの初期化 (5) _Jv_InitClass(Class)を直接呼び出す Initializer::mainをCNIで作成 #include <stdio.h> #include "Initializer.h" extern "C" void *_ZN9ClassInit6class$E; extern "C" void _Jv_InitClass(void *); void Initializer::main(JArray<java::lang::String *> *) { puts("init >>"); _Jv_InitClass(&_ZN9ClassInit6class$E); puts("<< init"); }
GCJにおけるクラスの初期化 (6) コンパイルして実行 予想通りの場所に“clinit”の表示 $ gcj --main=Initializer ClassInit.java Initializer.java cni.cc $ ./a.out init >> clinit << init
GCJにおけるクラスの初期化 (7) _Jv_InitClassを使うとクラスを初期化できる 次のことが可能になった コンパイラのソースlibjava/prims.ccで定義 次のことが可能になった インスタンスの生成 クラスメソッドの呼び出し (定数でない) クラスフィールドの参照
インスタンスの生成 (1) 簡単なソースコードを書いて検証 コンパイルしてアセンブルファイルを読む public class New { int a, b; public New(int a, int b) { this.a = a; this.b = b; } public static void main(String[] args) { new New(123, 456); } }
インスタンスの生成 (2) _ZN3New4mainEP6JArrayIPN4java4lang6StringEE: .. pushl $_ZN3New6class$E call _Jv_InitClass .. pushl $4 pushl $_ZN3New6class$E call _Jv_AllocObjectNoFinalizer .. pushl $456 pushl $123 pushl %eax call _ZN3NewC1Eii …
インスタンスの生成 (3) インスタンスの生成は次の3ステップ クラスの初期化 インスタンス領域の割り当て コンストラクタの呼び出し _Jv_InitClass 引数にclass インスタンス領域の割り当て _Jv_AllocObjectNoFinalizer 引数にclass(, インスタンスのサイズ) コンストラクタの呼び出し _ZN3NewC1Eii -> New::New(int, int) 第一引数に割り当てたインスタンス
インスタンスの生成 (4) 3つのステップを忠実に再現 クラス初期化、メモリ割り当て、コンストラクタ呼出し #include "Initializer.h" ... void Initializer::main(JArray<java::lang::String *> *) { /* 1 */ _Jv_InitClass(&_ZN3New6class$E); /* 2 */ void *obj = _Jv_AllocObjectNoFinalizer( (java::lang::Class*)&_ZN3New6class$E, 4); /* 3 */ _ZN3NewC1Eii(obj, 123, 456); }
インスタンスの生成 (5) 成功したかどうか分からないので、フィールドの値を表示するようにしてみる
インスタンスの生成 (6) 構造を無理矢理たどって再現 #include <stdio.h> #include "Initializer.h“ ... void Initializer::main(JArray<java::lang::String *> *) { /* 1 */ _Jv_InitClass(&_ZN3New6class$E); /* 2 */ void *obj = _Jv_AllocObjectNoFinalizer( (java::lang::Class*)&_ZN3New6class$E, 4); /* 3 */ _ZN3NewC1Eii(obj, 123, 456); printf("a=%d, b=%d\n", ((int *)obj)[1], ((int *)obj)[2]); }
インスタンスの生成 (7) 実行してみる $ gcj --main=Initializer New.java Initializer.java cni.cc $ ./a.out a=123, b=456
次回 興味のあるところから ポリモーフィズムの実現 Javaの名前空間とオブジェクトファイルの名前空間 クラスの初期化 インスタンスの生成 クラスの登録 配列の扱い 例外の処理 synchronizeの処理 インスタンスの破棄 ガーベジコレクタとの調和