這次來看一個簡單的偽隨機函數生成器,之前我在書中提到過一次。
#!java
public class LCG
{
public static int rand_state;
public void my_srand (int init)
{
rand_state=init;
}
public static int RNG_a=1664525;
public static int RNG_c=1013904223;
public int my_rand ()
{
rand_state=rand_state*RNG_a;
rand_state=rand_state+RNG_c;
return rand_state & 0x7fff;
}
}
在上面的代碼中我們可以看到開始的地方有兩個類字段被初始化。不過java究竟是如何進行初始化的呢,我們可以通過javap的輸出看到類構造的方式。
static {};
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #5 // int 1664525
2: putstatic #3 // Field RNG_a:I
5: ldc #6 // int 1013904223
7: putstatic #4 // Field RNG_c:I
10: return
從上面的代碼我們可以直觀的看出變量如何被初始化,RNG_a和iRNG_C分別占用了第三以及第四儲存位,并使用puststatic指令將常量put進儲存位置。
下面的my_srand()函數將輸入值存儲到rand_state中;
public void my_srand(int);
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: iload_1
1: putstatic #2 // Field ?
? rand_state:I
4: return
iload_1 取得輸入值并將其壓入棧。但為什么不用iload_0? 因為函數中可能使用了類字段,所以這個變量被作為第0個參數傳遞給了函數,我們可以看到rand_state字段在類中占用第二個儲存位。之前的putstatic會從棧頂復制數據并且將其壓入第二儲存位。
現在的my_rand():
public int my_rand();
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field ?
? rand_state:I
3: getstatic #3 // Field RNG_a:I
6: imul
7: putstatic #2 // Field ?
? rand_state:I
10: getstatic #2 // Field ?
? rand_state:I
13: getstatic #4 // Field RNG_c:I
16: iadd
17: putstatic #2 // Field ?
? rand_state:I
20: getstatic #2 // Field ?
? rand_state:I
23: sipush 32767
26: iand
27: ireturn
它僅是加載了所有對象字段的值。并且用putstatic指令對rand_state的值進行更新。
因為之前我們通過putstatic指令將rand_state的值丟棄,所以在20行的位置,再次加載rand_state值。這種方式其實效率不高,不過我們還是要承認jvm在某些地方所做的優化還是很不錯的。
我們來舉個條件跳轉的栗子:
#!java
public class abs
{
public static int abs(int a)
{
if (a<0)
return -a;
return a;
}
}
public static int abs(int);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: iload_0
1: ifge 7
4: iload_0
5: ineg
6: ireturn
7: iload_0
8: ireturn
上面代碼中ifge指令的作用是:當棧頂的值大于等于0的時候跳轉到偏移位7,需要注意的是,任何的ifXX指令都會將棧中的值彈出用于進行比較。
現在來看另外一個例子
#!java
public static int min (int a, int b)
{
if (a>b)
return b;
return a;
}
我們得到的是:
public static int min(int, int);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: if_icmple 7
5: iload_1
6: ireturn
7: iload_0
8: ireturn
if_icmple會從棧中彈出兩個值進行比較,如果第二個小于或者等于第一個,那么跳轉到偏移位7.
我們看另一個max函數的例子:
#!java
public static int max (int a, int b)
{
if (a>b)
return a;
return b;
}
從下面可以看出代碼都差不多,唯一的區別是最后兩個iload指令(偏移位5和偏移位7)互換了。
public static int max(int, int);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: iload_0
1: iload_1
2: if_icmple 7
5: iload_0
6: ireturn
7: iload_1
8: ireturn
更復雜的例子。。
#!java
public class cond
{
public static void f(int i)
{
if (i<100)
System.out.print("<100");
if (i==100)
System.out.print("==100");
if (i>100)
System.out.print(">100");
if (i==0)
System.out.print("==0");
}
}
public static void f(int);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: iload_0
1: bipush 100
3: if_icmpge 14
6: getstatic #2 // Field java/?
? lang/System.out:Ljava/io/PrintStream;
9: ldc #3 // String <100
11: invokevirtual #4 // Method java/io?
? /PrintStream.print:(Ljava/lang/String;)V
14: iload_0
15: bipush 100
17: if_icmpne 28
20: getstatic #2 // Field java/?
? lang/System.out:Ljava/io/PrintStream;
23: ldc #5 // String ==100
25: invokevirtual #4 // Method java/io?
? /PrintStream.print:(Ljava/lang/String;)V
28: iload_0
29: bipush 100
31: if_icmple 42
34: getstatic #2 // Field java/?
? lang/System.out:Ljava/io/PrintStream;
37: ldc #6 // String >100
39: invokevirtual #4 // Method java/io?
? /PrintStream.print:(Ljava/lang/String;)V
42: iload_0
43: ifne 54
46: getstatic #2 // Field java/?
? lang/System.out:Ljava/io/PrintStream;
49: ldc #7 // String ==0
51: invokevirtual #4 // Method java/io?
? /PrintStream.print:(Ljava/lang/String;)V
54: return
if_icmpge出棧兩個值,并且比較兩個數值,如果第的二個值大于第一個,跳轉到偏移位14,if_icmpne和if_icmple做的工作類似,但是使用不同的判斷條件。
在行偏移43的ifne指令,它的名字不是很恰當,我更愿意把它命名為ifnz(if not zero 可能是冷笑話)(如果棧定的值不是0則跳轉),當不是0的時候,跳轉到偏移54,如果輸入的值不是另,如果是0,執行流程進入偏移46,并且打印字符串“==0”。
JVM沒有無符號數據類型,所以,只能通過符號整數值進行比較指令操作。
讓我們稍微擴展一下min()和max()這個例子。
#!java
public class minmax
{
public static int min (int a, int b)
{
if (a>b)
return b;
return a;
}
public static int max (int a, int b)
{
if (a>b)
return a;
return b;
}
public static void main(String[] args)
{
int a=123, b=456;
int max_value=max(a, b);
int min_value=min(a, b);
System.out.println(min_value);
System.out.println(max_value);
}
}
下面是main()函數的代碼。
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: bipush 123
2: istore_1
3: sipush 456
6: istore_2
7: iload_1
8: iload_2
9: invokestatic #2 // Method max:(II?
? )I
12: istore_3
13: iload_1
14: iload_2
15: invokestatic #3 // Method min:(II?
? )I
18: istore 4
20: getstatic #4 // Field java/?
? lang/System.out:Ljava/io/PrintStream;
23: iload 4
25: invokevirtual #5 // Method java/io?
? /PrintStream.println:(I)V
28: getstatic #4 // Field java/?
? lang/System.out:Ljava/io/PrintStream;
31: iload_3
32: invokevirtual #5 // Method java/io?
? /PrintStream.println:(I)V
35: return
棧中的參數被傳遞給其他函數,并且將返回值置于棧頂。
java中的位操作其實與其他的一些ISA(指令集架構)類似:
#!java
public static int set (int a, int b)
{
return a | 1<<b;
}
public static int clear (int a, int b)
{
return a & (~(1<<b));
}
public static int set(int, int);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=2
0: iload_0
1: iconst_1
2: iload_1
3: ishl
4: ior
5: ireturn
public static int clear(int, int);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=2
0: iload_0
1: iconst_1
2: iload_1
3: ishl
4: iconst_m1
5: ixor
6: iand
7: ireturn
iconst_m1加載-1入棧,這數其實就是16進制的0xFFFFFFFF,將0xFFFFFFFF作為XOR-ing指令執行的操作數。起到的效果就是把所有bits位反向,(A.6.2在1406頁)
我將所有數據類型,擴展成64為長整型。
#!java
public static long lset (long a, int b)
{
return a | 1<<b;
}
public static long lclear (long a, int b)
{
return a & (~(1<<b));
}
public static long lset(long, int);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=2
0: lload_0
1: iconst_1
2: iload_2
3: ishl
4: i2l
5: lor
6: lreturn
public static long lclear(long, int);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=2
0: lload_0
1: iconst_1
2: iload_2
3: ishl
4: iconst_m1
5: ixor
6: i2l
7: land
8: lreturn
代碼是相同的,但是操作64位值的指令的前綴變成了L,并且第二個函數參數還是int類型,當32位需要升級為64位值時,會使用i21指令把整型擴展成64位長整型.
#!java
public class Loop
{
public static void main(String[] args)
{
for (int i = 1; i <= 10; i++)
{
System.out.println(i);
}
}
}
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_1
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpgt 21
8: getstatic #2 // Field java/?
? lang/System.out:Ljava/io/PrintStream;
11: iload_1
12: invokevirtual #3 // Method java/io?
? /PrintStream.println:(I)V
15: iinc 1, 1
18: goto 2
21: return
icont_1將1推入棧頂,istore_1將其存入到局部數組變量的儲存位1。
可以注意到沒有使用第0個儲存位,因為main()函數只有一個指向其的引用的參數(String數組),就位于第0號槽中。
因此,本地變量i 總是在第1儲存位中。 在行偏移3和行偏移5的位置,指令將i和10進行比較。如果i大于10,執行流將進入偏移21,之后函數會結束,如果i小于或等于10,則調用println。我們可以看到i在偏移11進行了重新加載,用于調用println。
多說一句,我們調用pringln打印數據類型是整型,我們看注釋,“i,v”,i的意思是整型,v的意思是返回void。
當println函數結束時,i進入偏移15,通過指令iinc將參數槽1的值,數值1與本地變量相加。
goto指令就是跳轉,它跳轉偏移2,就是循環體的開始地址.
下面讓我們來處理更復雜的例子
#!java
public class Fibonacci
{
public static void main(String[] args)
{
int limit = 20, f = 0, g = 1;
for (int i = 1; i <= limit; i++)
{
f = f + g;
g = f - g;
System.out.println(f);
}
}
}
#!bash
?public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: bipush 20
2: istore_1
3: iconst_0
4: istore_2
5: iconst_1
6: istore_3
7: iconst_1
8: istore 4
10: iload 4
12: iload_1
13: if_icmpgt 37
16: iload_2
17: iload_3
18: iadd
19: istore_2
20: iload_2
21: iload_3
22: isub
23: istore_3
24: getstatic #2