一、概述
1.1、作用
算术指令用于对操作数栈栈顶的元素(如果运算只包含一个操作数,那么该元素就是栈顶的元素,如果有两个操作数,那么就是栈顶和次栈顶的元素)进行某种特定的运算,并把运算的结果重新压入操作数栈
1.2、分类
大体上来说,算术指令可以分为两大类,一类是对 整数类型 的数据进行运算的指令,另外一类是对 浮点数类型 的数据进行运算的指令
1.3、运算类型
在 Jvm 中没有直接支持 byte、short、char、boolean 类型的算术指令,对于这些数据类型统一使用 int 类型的指令来进行处理,Java 虚拟机中的实际类型和参与运算类型的对照表如下
实际的数据类型 | Jvm 指令运算时使用的数据类型 |
byte | int |
short | int |
char | int |
boolean | int |
int | int |
float | float |
double | double |
long | long |
reference | reference |
returnAddress | returnAddress |
二、指令
加法指令: iadd、fadd、dadd、ladd
减法指令: isub、fsub、dsub、lsub
乘法指令: imul、fmul、dmul、lmul (mul 是 multiply 的简写,代表乘法的意思)
除法指令: idiv、fdiv、ddiv、ldiv (div 是 divide 的简写,代表除法的意思)
求余指令: irem、frem、drem、lrem (rem 是 remainder 的简写,代表除法的意思)
取反指令: ineg、fneg、dneg、lneg(neg 是 negation 的简写,代表取反的意思)
自增指令: iinc (注意该指令只针对 int 类型的数据,其它类型的数据自增不使用 iinc 指令,使用的与常数 1 相加的操作,iinc 是在局部变量表上直接做自增的操作,并不是在操作数栈上做自增的操作)
三、案例
3.1、i++ 和 ++i 的区别
public void method() { int i = 10; i++; }
编译之后的字节码指令如下
public void method() { int i = 10; ++i; }
可以看出如果不做赋值操作的情况下 i++ 和 ++i 的字节码指令是一样的,所以我们最常见的 for 循环代码,下面两种写法是等价的
// 写法 1 for (int i = 0; i < 10; i++) { } // 写法 2 for (int i = 0; i < 10; ++i) { }
3.2、i++ 和 ++i 做赋值操作
public void method() { int i = 0; i = i++; System.out.println(i); }
对应的字节码文件
指令分析过程
指令 1、iconst_0: 将常数 0 压入操作数栈栈顶的位置
指令 2、istore_1: 将操作数栈栈顶的元素 0 弹出操作数栈,存入 slot1 位置
指令 3、iload_1: 将局部变量表 slot1 中的变量压入操作数栈栈顶位置
指令 4、iinc 1 by 1: 将 slot1 中存储的变量做 +1 操作
指令 5、istore_1: 将操作数栈栈顶的元素 0 弹出操作数栈,存储在局部变量表 slot1 位置
指令 6、getstatic: 获取静态字段 System.out,并将其压入操作数栈当中
指令 7、将局部变量表中 slot1 位置的元素压入操作数栈栈顶位置
指令 8、将操作数栈中的 0 和 System.out 弹出操作数栈,并调用 println() 方法进行打印输出操作,此时输出的结果为 0
然后将 i++ 换成 ++i
public void method() { int i = 0; i = ++i; System.out.println(i); }
对应的字节码文件
指令分析过程
指令 1、iconst_0: 将常数 0 压入操作数栈栈顶的位置
指令 2、istore_1: 将操作数栈栈顶的元素 0 弹出操作数栈,存入 slot1 位置
指令 3、iinc 1 by 1: 将局部变量表 slot1 位置的变量做 +1 操作
指令 4、iload_1: 将局部变量表中 slot1 位置的变量压入操作数栈栈顶位置
指令 5、istore_1: 将操作数栈栈顶的元素 1 弹出操作数栈,存放在局部变量表 slot1 位置
指令 6、getstatic: 获取静态字段 System.out,并将其压入操作数栈当中
指令 7、将局部变量表中 slot1 位置的元素压入操作数栈栈顶位置
指令 8、将操作数栈中的 1 和 System.out 弹出操作数栈,并调用 println() 方法进行打印输出操作,此时输出的结果为 1
从上面字节码的执行过程中可以看出 i++ 和 ++i 的区别
i++: 局部变量表中的操作数 0 先入操作数栈,然后再在局部变量表上做自增操作变成 1,最后给 i 赋值的时候操作数栈中的 0 覆盖了局部变量表中的 1,最终结果为 0 (先入操作数栈,然后在局部变量表自增 1)
++i: 局部变量表中的操作数先做自增操作变成 1,然后将 1 压入操作数栈中,最后给 i 赋值的时候操作数栈中的 1 覆盖了局部变量表中的 1,最终结果为 1 (先在局部变量表自增 1,然后再入操作数栈)
3.3、综合案例
public static void method() { int i = 0; i = i++ + ++i; System.out.println(i); }
上述代码编译之后反解析后的字节码指令如下
0 iconst_0 1 istore_1 2 iload_1 3 iinc 1 by 1 6 iinc 1 by 1 9 iload_1 10 iadd 11 istore_1 12 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;> 15 iload_1 16 invokevirtual #3 <java/io/PrintStream.println : (I)V> 19 return
method 是非静态方法,所以该方法栈帧的局部变量表第一个槽位(slot0)存放的是 this 指针的内存地址值,除了 this 指针外整个方法只有一个 int 类型的局部变量 i,故局部变量表的最大槽位数为 2,方法中只有加法操作,所以操作数栈的最大深度为 2
指令 1、iconst_0: 将 int 类型的常数 0 压入操作数栈
指令 2、istore_1: 将操作数栈的栈顶元素(0)弹出操作数栈,然后存放在局部变量表索引下标为 1 的位置(slot1 槽位)
指令 3、iload_1: 将局部变量表 slot1 的变量值压入操作数栈的栈顶位置
指令 4、iinc 1 by 1: 将局部变量表下标为 1 的变量做加 1 操作
指令 5、iinc 1 by 1: 将局部变量表下标为 1 的变量做加 1 操作
指令 6、iload_1: 将局部变量表下标为 1 的槽位中的变量压入操作数栈的栈顶位置,原先已经在栈位置的常数 0 就会被压入到次栈顶的位置
指令 7、iadd: 将位于栈顶的操作数 2 和位于次栈顶的操作数 0 弹出操作数栈,执行加法计算,计算完成之后将结果重新存回操作数栈的栈顶位置
指令 8、istore_1: 将位于操作数栈栈顶位置的元素 2 弹出操作数栈,存入局部变量表 slot1 位置
指令 9、getstatic: 获取静态字段 System.out,并将其压入操作数栈当中
指令 10、iload_1: 将局部变量表 slot1 中的变量压入操作数栈当中
指令 11、invokevirtual: 将操作数栈中的 System.out 和 2 弹出操作数栈,调用 println() 方法进行打印
综上分析,上述代码执行之后 i 的值为 2
标签:02,__,操作数,压入,++,局部变量,int,指令 From: https://www.cnblogs.com/xiaomaomao/p/16930829.html