美文网首页
i++跟++i在JVM字节码上的区别

i++跟++i在JVM字节码上的区别

作者: YocnZhao | 来源:发表于2021-03-05 16:43 被阅读0次

    大家都知道i++跟++i的区别:

    1. i++是先赋值再运算
    2. ++i是先运算再赋值

    那可能很多人没有写过i=i++或者i=++i,这样的骚语句,这个时候是什么样的情况呢?
    可能很多人能想到i=i++,结果i=0,因为先赋值,这时候值是0,所以i=0。
    i=++i最终结果i=1,因为是先运算+1再赋值,这时候已经是1了。
    那到底是做了什么样的操作来实现这样的结果的呢?我们来追根溯源,正常情况,我们应该先看一下class文件显示什么东西,那我们写了几个最简单的方法如下:

    public class TestI {
        public void testMethod() {
            int i = 0;
            i = i + 2;
        }
        public void testMethodA() {
            int i = 0;
            i = i++;
        }
        public void testMethodB() {
            int i = 0;
            i = ++i;
        }
    }
    

    这里为了明白正常的i+1在java中是怎么处理的,增加了一个i=i+2的对照组。
    我们用javac TestI.java编译成class文件,如下:

        public void testMethod() {
            byte var1 = 0;
            int var2 = var1 + 2;
        }
    
        public void testMethodA() {
            byte var1 = 0;
            int var2 = var1 + 1;
        }
    
        public void testMethodB() {
            byte var1 = 0;
            int var2 = var1 + 1;
        }
    

    testMethodA竟然跟testMethodB是一样的。Emmm,因吹斯听,应该是IDE在翻译字节码的时候没有看出来这俩的区别?那我们还是来看字节码好了。
    javap -c TestI.class命令查看字节码如下:

    这里需要的知识储备是JVM指令集JVM 栈帧之操作数栈与局部变量表,默认读者了解不在赘述。

    public class testJava.TestI {
      public testJava.TestI();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public void testMethod();
        Code:
           0: iconst_0                          //将int型0压栈至栈顶
           1: istore_1                          //将栈顶int型数值存入第1个本地变量
           2: iload_1                           //将第1个int型本地变量压栈至栈顶
           3: iconst_2                          //将int型2压栈至栈顶
           4: iadd                              //栈顶两个数相加并进栈
           5: istore_1                          //将栈顶int型数值存入第1个本地变量
           6: return                            //从当前方法返回void
    
      public void testMethodA();
        Code:
           0: iconst_0                          //将int型0压栈至栈顶
           1: istore_1                          //将栈顶int型数值存入第1个本地变量
           2: iload_1                           //将第1个int型本地变量压栈至栈顶
           3: iinc          1, 1                //将指定int型变量增加指定值,可以有两个变量,分别表示index, const,index指第index个int型本地变量,const增加的值,所以是第1个本地变量增加1
           6: istore_1                          //将栈顶int型数值存入第1个本地变量
           7: return                            //从当前方法返回void
    
      public void testMethodB();
        Code:
           0: iconst_0                          //将int型0压栈至栈顶
           1: istore_1                          //将栈顶int型数值存入第1个本地变量
           2: iinc          1, 1                //将指定int型变量增加指定值,第1个本地变量增加1
           5: iload_1                           //将第1个int型本地变量压栈至栈顶
           6: istore_1                          //将栈顶int型数值存入第1个本地变量
           7: return                            //从当前方法返回void
    }
    

    我们做了一个最简单的i=i+2的对照组来看正常情况下这个最简单的增量操作是长什么样子的,它的具体流程是先把i在本地变量表里面初始化出来,再把i的值放到操作数栈,再给操作数栈放一个需要加的2,然后i跟2相加,得到的结果再存到本地变量完成相加操作。
    而i = i++或者i = ++i的操作还是跟i=i+1有很大区别的。最大的区别就是i=i+2是用了iadd,而i++是用了iinc,iadd是作用在操作数栈中的,而iinc是直接在本地变量表中直接把变量增加。
    来看i++++i,其实就是iinciload的运行顺序的区别,印证了我们之前所说的这两者的区别,我们再赘述一遍两者的区别,看是怎么体现出来的:

    1. i++是先赋值再运算
    2. ++i是先运算再赋值

    i++是先iload_1把0这个值推到操作数帧顶部,再iinc把本地变量表里面的i做+1操作,这个操作结束后意味着这时候在操作数栈里面代表i的值仍然是+1之前的值也就是0,而其实本地变量表中的值已经是1,但是得下一个再iload1的时候才是1代表i出战。
    而++i,正好相反是先iinc做+1操作,然后再代表i出证,这时候的值已经变成了1。
    所以后面执行到i=i++还是i=++i的共同代码i=,在字节码中也就是istore_1,把这时候栈中代表i出征的值赋值回本地变量表中的i,所以这时候i=i++操作数栈里面的0覆盖了本地变量表中的1。

    所以i=i++比i++做得多此一举事情就是多做了一个i=, 把操作数栈中的0覆盖了本地变量表中的正确值1.

    说一千道一万,什么都比不上一张图来的直观:

    i++2.png

    那既然i++是先赋值再运算,那我们多做几次i=i++是不是就好了,比如加个while循环,like below:

        public void testMethodC() {
            int i = 0;
            for(int j = 0;j<100;j++){
                i = i++;
            }
        }
    
      public void testMethodC();
        Code:
           0: iconst_0                          //将int型0压栈至栈顶
           1: istore_1                          //将栈顶int型数值存入第1个本地变量
           2: iconst_0                          //将int型0压栈至栈顶
           3: istore_2                          //将栈顶int型数值存入第2个本地变量
           4: iload_2                           //将第2个int型本地变量推送至栈顶
           5: bipush        100                 //将将单字节的常量值100推送至栈顶
           7: if_icmpge     21                  //栈顶弹出两个值,比较两int型数值大小,当结果大于等于0时跳转到21
          10: iload_1                           //将第1个int型本地变量推送至栈顶
          11: iinc          1, 1                //将指定int型变量增加指定值,第1个本地变量增加1
          14: istore_1                          //将栈顶int型数值存入第1个本地变量
          15: iinc          2, 1                //将指定int型变量增加指定值,第2个本地变量增加1
          18: goto          4                   //跳转到偏移位4
          21: return
    

    其实想一想还是0哈,因为每次的i都是重新load到操作数栈的,之前的+1过来的又会被覆盖,不管循环多少次都是一样的:


    while3.png

    总结:

    1. i=i+1用的是iadd指令,作用是弹出操作数栈顶部的两个值相加并把结果再压入栈顶。
    2. i++用的是iinc指令,直接操作的是本地变量表的数值。
    3. i=i++结果是0是因为本地变量表中i加一了但是操作数栈中的仍然是0,最后赋值的时候0把1覆盖掉了。

    相关文章

      网友评论

          本文标题:i++跟++i在JVM字节码上的区别

          本文链接:https://www.haomeiwen.com/subject/zetdqltx.html