コンパイラの解析 (3)
クラスとインスタンスの初期化
Suguru ARAKAWA
Faculty of Computer and Information Sciences,
Hosei University
1
Table of Contents
シンボル名
クラスの初期化
インスタンスの生成
2
シンボル名
JavaやC++のシンボルは名前マングルがかかった
状態でオブジェクトファイルに格納される
C言語のシンボルよりも多くの情報を含む
 名前空間
 引数情報
3
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
4
名前デマングルツール
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)
5
名前マングルの略式法則
フィールド
 _Z<フィールド名>
メソッド
 _Z<メソッド名><引数1><引数2>…
<>一つ分で一つのシンボル/基本型を表す
6
名前マングル – シンボル
通常のシンボルは次の形式でマングル
 <名前文字数><名前>
例
a
-> 1a
 sample -> 6sample
7
名前マングル – 名前空間つきシンボル
名前空間つきのシンボル
 N<シンボル1><シンボル2>…E
例
 a::b
-> N1a1bE
 java::lang::Math -> N4java4lang4MathE
8
名前マングル – 基本型
基本型は1文字にマングルされる
 int
-> i
 double -> d
 void -> v
…
メソッドのマングル時、引数に利用
 foo()
は foo(void) と常に解釈
9
名前マングル – ポインタ
ポインタは次の形式でマングル
 P<シンボル>
例
 int
* -> Pi
 int ** -> Pii
 java::lang::Object*
-> PN4java4lang6ObjectE
10
名前マングル – テンプレート
テンプレートは次の形式
 I<シンボル1><シンボル2>…E
例
 JArray<int>
-> 6JArrayIiE
 JArray<Hoge> -> 6JArrayIN4HogeEE
11
名前マングル – ここまでの例
 フィールド

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
12
名前マングル – ここまでの例 (分解)
 Sample::main(JArray<java::lang::String *>*)
_Z
N 6Sample 4main E
P 6JArray
I
P N 4java 4lang 6String E
E
13
名前マングル – 省略形
 同じ名前にでてくるシンボルは再利用できる


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_
14
クラスの初期化 (1)
ここの部分はどうコンパイルされる?
 変数の初期化用コード
public class StaticInit {
public static double SQRT2 = Math.sqrt(2);
}
15
クラスの初期化 (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
16
クラス初期化子 (1)
Static initializer (クラス初期化子)
public class ClassInit {
static {
System.out.println("clinit");
}
public static void main(String[] args) {
System.out.println("main");
}
}
17
クラス初期化子 (2)
実行すると、mainメソッドの前に呼び出される
$ java ClassInit
clinit
main
18
クラス初期化子 (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
19
クラス初期化のルール
 Java Virtual Machine Specification 2nd

次の場合、クラスTはVMによって初期化される
1.
2.
3.
4.

Tの子クラスを初期化する直前
Tのインスタンスを生成する直前
Tのクラスメソッドを起動する直前
Tの定数でないクラス変数を参照する直前
同じクラスは1度しか初期化されない
 →Mainメソッドを呼び出す直前に初期化
20
一息 (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);
}
}
21
一息 (2)
トレースしてみる
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);
}
}
クラスメソッドの起動
→Subの初期化
子クラスの初期化
→Circuの初期化
S2は非定数
→初期化までnull
S2は未初期化
→S1 == null
22
一息 (3)
“null”と表示される
 Circu.S1初期化の時点でSub.S2が未初期化
$ java Sub
null
23
閑話休題
gcjでは明示的にクラスの初期化をする
 gcjではコンパイル済みコードを実行
クラスの初期化方法を解析
24
GCJにおけるクラスの初期化 (1)
他のクラスを初期化するコードをコンパイル
 クラスメソッドの呼び出しはクラス初期化の原因
 java.lang.Mathを初期化する
public class GcjClinit {
public static double sqrt(double d) {
return Math.sqrt(d);
}
}
25
GCJにおけるクラスの初期化 (2)
コンパイルしたものを分析
$ gcj -S GcjClinit.java
_ZN9GcjClinit4sqrtEd:
.. (prologue)
pushl $_ZN9GcjClinit6class$E
call _Jv_InitClass
..
call _ZN4java4lang4Math4sqrtEd
.. (epilogue)
26
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
27
GCJにおけるクラスの初期化 (4)
CNIからクラスの初期化を行う実験
 下記のクラスをCNIから初期化する
public class ClassInit {
static {
System.out.println("clinit");
}
public static void main(String[] args) {
System.out.println("main");
}
}
28
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");
}
29
GCJにおけるクラスの初期化 (6)
コンパイルして実行
 予想通りの場所に“clinit”の表示
$ gcj --main=Initializer ClassInit.java Initializer.java cni.cc
$ ./a.out
init >>
clinit
<< init
30
GCJにおけるクラスの初期化 (7)
_Jv_InitClassを使うとクラスを初期化できる
 コンパイラのソースlibjava/prims.ccで定義
次のことが可能になった
 インスタンスの生成
 クラスメソッドの呼び出し
 (定数でない)
クラスフィールドの参照
31
インスタンスの生成 (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);
}
}
32
インスタンスの生成 (2)
_ZN3New4mainEP6JArrayIPN4java4lang6StringEE:
..
pushl $_ZN3New6class$E
call _Jv_InitClass
..
pushl $4
pushl $_ZN3New6class$E
call _Jv_AllocObjectNoFinalizer
..
pushl $456
pushl $123
pushl %eax
call _ZN3NewC1Eii …
33
インスタンスの生成 (3)
 インスタンスの生成は次の3ステップ
1.
クラスの初期化


2.
インスタンス領域の割り当て


3.
_Jv_InitClass
引数にclass
_Jv_AllocObjectNoFinalizer
引数にclass(, インスタンスのサイズ)
コンストラクタの呼び出し


_ZN3NewC1Eii -> New::New(int, int)
第一引数に割り当てたインスタンス
34
インスタンスの生成 (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);
}
35
インスタンスの生成 (5)
成功したかどうか分からないので、フィールドの値
を表示するようにしてみる
36
インスタンスの生成 (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]);
}
37
インスタンスの生成 (7)
実行してみる
$ gcj --main=Initializer New.java Initializer.java cni.cc
$ ./a.out
a=123, b=456
38
次回
 興味のあるところから










ポリモーフィズムの実現
Javaの名前空間とオブジェクトファイルの名前空間
クラスの初期化
インスタンスの生成
クラスの登録
配列の扱い
例外の処理
synchronizeの処理
インスタンスの破棄
ガーベジコレクタとの調和
39
ダウンロード

libgcj3