首页 > 其他分享 >JVM 指令02__算术指令

JVM 指令02__算术指令

时间:2022-11-27 22:12:39浏览次数:35  
标签:02 __ 操作数 压入 ++ 局部变量 int 指令

一、概述

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

相关文章