关于字节码:我所知道JVM虚拟机之字节码指令集与解析二算数指令

28次阅读

共计 3694 个字符,预计需要花费 10 分钟才能阅读完成。

前言

前篇文章解说了局部变量压栈指令、常量入栈指令以及出栈装入局部变量表指令,那么本篇文章接着解说算数指令,让咱们开始吧

一、算数指令概述


作用

================================

算术指令用于对 两个操作数栈上的值进行某种特定运算 ,并把 后果从新压入操作数栈

分类

================================

大体上算术指令能够分为两种: 对 整型数据 进行运算的指令与对 浮点类型 数据进行运算的指令。

byte、short、char 和 booleanl 类型阐明

================================

在每一大类中,都有针对 Java 虚拟机具体数据类型的专用算术指令。

但没有 间接反对 byte、short、char 和 boolean 类型的算术指令 ,对于这些数据的运算都 应用 int 类型的指令来解决。此外,在解决 boolean、byte、short 和 char 类型的数组时,也会转换为应用对应的 int 类型的字节码指令来解决。

运算时的溢出

================================

数据运算可能会导致溢出,例如两个很大的正整数相加,后果可能是一个正数。

其实 Java 虚拟机标准并无明确规定过整型数据溢出的具体后果,仅规定了在解决整型数据时,只有 除法指令以及求余指令中当呈现除数为 0 时会导致虚拟机抛出异样ArithmeticException

运算模式

================================

向最靠近数舍入模式:

JVM 要求在进行 浮点数计算 时,所有的运算后果都 必须舍入到适当的精度 非准确后果必须舍入为可被示意的最靠近的准确值,如果有两种可示意的模式与该值一样靠近,将优先选择最低无效位为零的

向零舍入模式:

将浮点数转换为整数时 采纳该模式,该模式将在指标数值类型中 抉择一个最靠近然而不大于原值的数字作为最准确的舍入后果

NaN 值应用

================================

当一个操作产生溢出时,将会应用有符号的无穷大示意,如果某个操作后果 没有明确的数学定义的话,将会应用 NaN 值来示意。而且所有应用 NaN 值作为操作数的算术操作,后果都会返回 NaN;

接下来咱们能够应用示例代码来领会一下 NaN 和 除数抛出异样的示例代码

public class ArithmeticTest {
    @Test
    public void method1(){
        int i = 10;
        double j = i / 0;
        system.out.print1n(j);
    }
}

// 输入后果如下:java.lang.Arithmeticception:/ by zero
at com.atguigu.java.ArithmeticTest.method1(ArithmeticTest.java:15)<22 internal calls>

此时咱们将除数 0 改成 double 类型的 0.0,在运行一下后果看看

public class ArithmeticTest {
    @Test
    public void method1(){
        int i = 10;
        double j = i / 0.0;
        system.out.print1n(j);// 无穷大
        
        double d1 = 0.0; I
        double d2 = d1 / 0.0;
        system.out.println(d2);//NaN: not a number
    }
}

// 输入后果如下:Infinity
NaN

咱们说除数与被除数雷同的话,返回后果应该是 1,然而因为分子也是 0,所以无奈确认具体数值

二、算数指令的所有运算指令

指令介绍

================================

  • 加法指令: iadd、ladd、fadd、dadd
  • 减法指令: isub、lsub、fsub、dsub
  • 乘法指令: imul、lmul、fmul、dmul
  • 除法指令: idiv、ldiv、fdiv、ddiv
  • 求余指令: irem、lrem、frem、drem //remainder: 余数
  • 取反指令: ineg、lneg、fneg、dneg //negation: 取反
  • 自增指令: iinc
  • 位运算(位移指令): ishl、ishr、iushr、lsh1、lshr、lushr
  • 位运算(按位或指令): ior、lor
  • 位运算(按位与指令): iand、land
  • 位运算(按位异或指令): ixor、lxor
  • 比拟指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp

接下来采纳示例代码来演示一下下面提到的指令

public class ArithmeticTest {public void method2(){
        f1oat i = 10;
        float j = -i;
        i = -j;
    }
}

接下来咱们编译代码应用插件查看具体的字节码指令是怎么样的?

后面咱们提到过 float 类型的范畴数值是 0 -2,若超出能够采纳 ldc 指令压操作数栈

同时将操作数栈中栈顶元素弹出后,装入局部变量表的指定地位,针对 float 类型应用 fload_1

此时咱们进行运算操作,将局部变量表索引为:1 取出来进行操作

当咱们操作数进行取反操作后,此时将操作完的后果压入局部变量表新的地位上

此时咱们发现代码还有进行取反的操作,所以还是与之前统一并从新压入局部变量表

接下来再应用第二个示例代码来演示一下下面提到的指令

public class ArithmeticTest {public void method3(int j){
        int i = 100;
        i = i + 10;
    }
}

后面咱们提到过 int 类型的范畴数值由不同的范畴,具体范畴有具体的指令进行操作

上面咱们进行压入局部变量表的的指令,看看是怎么回事

与下面同样,咱们须要弹出进行运算并再次将后果放入原局部变量表的索引地位

若咱们批改一下示例代码,采纳 += 的形式进行减少操作看看具体的字节码会是什么?

public class ArithmeticTest {public void method3(int j){
        int i = 100;
        i += 10;
    }
}

接下来咱们编译代码应用插件查看具体的字节码指令是怎么样的?

依据字节码咱们进行剖析看看,具体 += 的形式做了什么指令?

接下来再应用第四个示例代码来演示一下下面提到的指令

public class ArithmeticTest {public int method4(){
        int a = 80;
        int b = 7;int c = 10;
        return (a + b)*c;
    }
}

接下来咱们编译代码应用插件查看具体的字节码指令是怎么样的?

相似的指令咱们就不再进行反复解说了,咱们关注具体的操作数指令

接下来再应用第五个示例代码来演示一下下面提到的指令

public class ArithmeticTest {public int method5(int i ,int j){return ( (i + j - 1) &~(j - 1));
    }
}

接下来咱们编译代码应用插件查看具体的字节码指令是怎么样的?

相似的指令咱们就不再进行反复解说了,咱们关注具体的操作数指令


接下来咱们应用示例代码,从字节码角度来演示一下 ++i

public class ArithmeticTest {// 对于(前)++ 和(后)++
    public void method6(){
        int i = 10;
        ++i;
    }
}

接下来咱们编译代码应用插件查看具体的字节码指令是怎么样的?

接下来咱们应用示例代码,从字节码角度来演示一下 i ++

public class ArithmeticTest {// 对于(前)++ 和(后)++
    public void method6(){
        int i = 10;
        i++;
    }
}

接下来咱们编译代码应用插件查看具体的字节码指令是怎么样的?

咱们发现没有波及其余运算符操作的时候,他们都是一样的字节码,这时咱们使用起来再看看

public class ArithmeticTest {public void method7(){
        int i = 10;
        int a = i++;
        int j = 20;
        int b = ++j;
    }
}

接下来咱们编译代码应用插件查看具体的字节码指令是怎么样的?


三、算数指令的比拟指令


比拟指令的阐明

================================

比拟指令的作用是 比拟栈顶两个元素的大小,并将比拟后果入栈

比拟指令有: dcmpg,dcmpl、fcmpg、fcmpl、lcmp

与后面解说的指令相似,首字符 d 示意 double 类型,f 示意 float,l 示意 long

对于 double 和 float 类型的数字,因为 NaN 的存在,各有两个版本的比拟指令

以 float 为例有 fcmpg 和 fcmpl 两个指令,区别在于在数字比拟时若遇到 NaN 值处理结果不同

指令 dcmpl 和 dcmpg 也是相似的,依据其命名能够揣测其含意,在此不再赘述

指令 lcmp 针对 long 型整数,因为 long 型整数没有 NaN 值,故无需筹备两套指令

举例比拟

================================

指令 fcmpg 和 fcmpl 都从栈中弹出两个操作数,并将它们做比拟

设栈顶的元素为 v2, 核顶顺位第 2 位的元素为 v1

  • 若 v1=v2 则压入 0
  • 若 v1>v2 则压入 1
  • 若 v1<v2 则压入 1

两个指令的不同之处在于,如果遇到 NaN 值,fcmpg 会压入 1,而 fcmpl 会压入 -1

正文完
 0