ASM指南翻译-8
?3方法
图3.1 一个包含3个帧的执行栈
?
图3.1展示了一个简单执行栈,包含3个帧。第一个帧包含3个局部变量,操作数栈大小为4,包含2个值。第二个帧包含2个局部变量,操作数栈中包含2个值。第三个帧位于执行栈的栈顶,包含4个局部变量和2个操作数。
?
当帧被创建的时候,操作数栈初始化为空,局部变量包含当前对象的引用this(非静态方法)以及方法的参数。例如a.equals(b)会创建一个帧,它包含一个空的操作数栈,第一个和第二个局部变量为a和b(其它的局部变量未被初始化)。
?
局部变量区和操作数栈区的每一个插槽(图3.1中的方框)都能存储java中的任何值,除了long型和double型的值。因为long和double值需要2个插槽,这增加了局部变量管理的复杂性:例如ith 方法的参数就没必要保存在局部变量i中(有疑问)。Math.max(1L,2L)会创建一个帧,第一个局部变量值为1L,占用前两个插槽,第二个局部变量值为2L,占据第三个和第四个插槽。
?
3.1.2字节码指令
一个字节码指令由一个指示指令的操作码和固定数量的参数:
?
opcode是一个无符号字节数值 字节码的名称是由助记符表示。例如,操作码0的助记符为NOP,其对应的指令不做任何事情。参数都是静态的值,定义了精确的指令行为。它们放置在操作码之后。例如,GOTO标签指令,它的操作码是167,需要一个参数label,这个label指定了下一条将被执行的指令。在这里不要将指令参数和指令操作数混淆:这里的参数是静态已知的并且被保存在编译后的代码中,而操作数的值来自操作数栈,它们在运行时才可知。?
字节码指令可以划分为两类:将局部变量的值传递到操作数栈的一小部分指令集,另外一部分指令在操作数栈上进行操作:它们取得栈顶上的一些数据,进行计算,获得结果,然后将结果放回栈顶。
?
ILOAD,LLOAD,FLOAD,DLOAD和ALOAD指令读取一个局部变量的值,然后将它放置到操作数栈,这些指令的参数就是需要读取的局部变量的索引值i。ILOAD被用来加载一个boolean,char,short以及int局部变量。LLOAD,FLOAD和DLOAD用来加载long,float或者double值,注意LLOAD和DLOAD实际加载了i和i+1插槽上的值。最后,ALOAD是用来加载那些非基本类型的值,如对象和数组引用。相对地,ISTORE,LSTORE,FSTORE,DSTORE和ASTORE指令是用来从操作数栈取得相应的数据然后保存会局部变量,它们的参数也是索引值i。
?
如你前面所见,XLOAD和XSTORE指令是区分类型的(事实上,包括你即将在下面看到的,几乎所有的指令都是区分类型的)。这样做主要是为了确保不进行非法的类型转换。当然,以不同的类型从局部变量中加载一个值也是合法的。例如,ISTORE 1 ALOAD 1这样的序列是合法的。当然,允许保存一个任意的内存地址到一个索引为1局部变量中,然后转换这个地址为一个对象引用。能够以不同于当前存储在局部变量中的类型来保存到另一个变量中,这样做是很完美的。这意味着一个局部变量的类型,或者存储在这个局部变量中值的类型,可以在方法的执行过程中改变。
?
如上面所说,其它的指令都是仅针对操作数栈进行操作的。它们可以分为以下几类(参看附录A.1):
Stack这些指令是用来操作栈中的数据:POP弹出栈顶的数据,DUP复制栈顶的数据然后放回栈顶,SWAP交换位于栈顶上的两个数据。
?
Constants这些指令用来将一个常量值放置到操作数栈:ACONST_NULL放置null值到栈顶,ICONST_0放置int类型的0到栈顶,FCONST_0放置float类型的0值到栈顶,DCONST_0放置double类型的0值,BIPUSH b放置字节值b到栈顶,SIPUSH s放置short类型的值s到栈顶。LDC cst放置int,float,long,double,String或者class类型的常量cst到栈顶。
?
算术和逻辑操作 这些指令从操作数栈弹出数值进行计算,然后将结果放回栈顶。它们没有任何参数。XADD,xSUB,Xmul,xDIV,和xREM与算术操作符+,-,*,/和%对应,这里的x为I,L,F或者D。同样地,这里还包含其它的指令,如<<,>>,|,&,^等针对int和long值的指令。
?
Casts 这些指令从栈弹出一个数据,然后将其转换为另外一个类型,然后返回栈顶。它们与java中的类型转换对应。I2F,F2D,L2D等,将数值从一种类型转换为另外一种类型。CHECKCAST t转换一个引用值类型为t。
?
Objects这些指令是用来创建对象,以及锁定对象,测试它们的类型等。例如,NEW type指令创建一个新对象,然后放回栈(type表示内部名称)。
?
Fields这些指令用来读取或者写入字段的值。GETFIELD owner name desc弹出一个对象的引用,然后将该对象字段名称为name的值放置到栈中。PUTFIELDowner name desc从栈中弹出一个值和一个对象引用,然后将这个值保存到这个对象的name字段中。在这两种情形中,对象必须是owner类型,它的字段必须是desc描述的类型。GETSTATIC和PUTSTATIC指令与上面指令类型,只是它们用作静态字段。
?
Methods这些指令用来调用方法或者构造方法。它们从栈顶弹出方法的参数,以及目标对象,然后将方法执行结果放回栈顶。INVOKEVIRTUAL owner name desc调用类型为owner 方法名为name,并且方法描述符为desc的方法。INVOKESTATIC用来调用静态方法。INVOKESPECIAL用来调用私有方法和构造方法。INVOKEINTERFACE用来调用定义在接口中的方法。
?
Arrays这些指令用来读取和写入数组中的值。xALOAD指令从栈上弹出一个索引值和一个数组,然后将索引值所对应的数组元素放回栈。xSTORE指令从站上弹出一个值,一个索引以及一个数组,然后将值保存到索引对应的数组元素中。这里的x可以是I,L,F,D或者A,也可以是B,C或者S。
?
Jumps这些指令在某些条件为真或者为假的时候,跳转到任意一条指令。它们是用来编译if,for,do,while,break和continue等指令的。例如,IFEQ label 从栈上弹出一个int值,如果这个值为0,就跳转到label表示的指令(否则执行流程将按正常流程执行下一条指令)。这里还有其它的一些跳转指令,如IFNE,或者IFGE等。最后,TABLESWITCH和LOOKUPSWITCH指令与java中的switch对应。
?
Return? xRETURN和RETURN指令是用来终止一个方法的执行,并将结果返回给调用者。RETURN针对无返回值的方法,而xRETURN针对其它有返回值的方法。
?