• 用java字节码解释i++和++i(转)


    这几天抽着一些时间,把Java的class文件结构研究了一下,再后来就想起了这个令人厌烦的问题,想从字节码指令的角度看看,java到底是怎么处理这个的

    先看一段java代码

    [java] view plain copy
     
    1. package bishi;  
    2.   
    3. public class PlusPlusTest {  
    4.       
    5.     public static void main(String[] args) {  
    6.   
    7.         int i = 1;  
    8.         i = i++ + i++ + i++;  
    9.         System.out.println(i);  
    10.         int j = 1;  
    11.         j = ++j + ++j + ++j;  
    12.         System.out.println(j);  
    13.     }  
    14.   
    15. }  


    打印的i和j分别是多少呢?

    先分析i++:

    [java] view plain copy
     
    1. int i = 1;  
    2. i = i++ + i++ + i++;  
    3. System.out.println(i);  


    这三句对应的字节码为:

    [java] view plain copy
     
    1. 0  iconst_1       //将int类型的数字1push到栈顶  
    2. 1  istore_1 [i]   //将栈顶的1存到变量i中, 这就是int i=1;  
    [java] view plain copy
     
    1.  2  iload_1 [i]    //把i的值再压栈,即栈顶存入1  
    2.  3  iinc 1 [i]   //把i变量中的值加1,此时i中存的是2,但是栈顶的值是1  
    3.  6  iload_1 [i]    //把i的值压栈,此时栈顶和栈顶第二个分别是2,1  
    4.  7  iinc 1 [i]   //把i变量中的值加1,此时i中存的是3,  
    5. 10  iadd           //把栈顶两个int类型的值相加,并把结果存入栈顶,即现在的栈,从栈顶往下依次存了3,2,1  
    6. 11  iload_1 [i]    //把i压栈,栈中自顶向下依次是3,3,2,1  
    7. 12  iinc 1 [i]   //把i变量中的值再加1,i中存的是4了  
    8. 15  iadd           //同10,执行完之后,栈顶为6  
    9. 16  istore_1 [i]   //将栈顶int型值存入i中,即执行完此步,i=6,到这一步就是整个 i = i++ + i++ + i++;  
    10. 17  getstatic java.lang.System.out : java.io.PrintStream [16]  
    11. 20  iload_1 [i]    //这一步把i中的6再次push到栈顶  
    12. 21  invokevirtual java.io.PrintStream.println(int) : void [22]   //输出栈顶的6  


    java虚拟机内存空间中存在一个叫java方法栈的区域,在这里为每个方法提供一个栈帧,在栈帧中存放了该方法的局部变量表,操作栈,动态链接,方法出口等信息。上面字节码中指令所指的栈应该就是这个操作栈吧(个人理解,不保证正确~)。

    分析完i++,下面来看看++j的原理

    [java] view plain copy
     
    1. int j = 1;  
    2. j = ++j + ++j + ++j;  
    3. System.out.println(j);  


    对应的字节码为:

    [java] view plain copy
     
    1. 24  iconst_1           
    2. 25  istore_2 [j]     //int j=1;  
    3. 26  iinc 1 [j]     //把j加1,j=2  
    4. 29  iload_2 [j]      //把j的值压栈,此时栈顶为2  
    5. 30  iinc 1 [j]     //把j加1,j=3  
    6. 33  iload_2 [j]      //把j的值压栈,栈顶往下依次为3,2  
    7. 34  iadd             //把栈顶两int类型数相加,结果压栈,栈顶往下:5,3,2  
    8. 35  iinc 1 [j]     //把j加1,j=4  
    9. 38  iload_2 [j]      //把j压栈,栈顶向下:4,5,3,2  
    10. 39  iadd             //把栈顶两int相加,结果压栈,栈顶往下:9,4,5,3,2  
    11. 40  istore_2 [j]     //把栈顶存入j,j=9,至此,j = ++j + ++j + ++j;执行完毕  
    12. 41  getstatic java.lang.System.out : java.io.PrintStream [16]  
    13. 44  iload_2 [j]  
    14. 45  invokevirtual java.io.PrintStream.println(int) : void [22]  


    总结

    i++和++i的区别就是在进入操作栈和自加的顺序,呃,说完怎么跟没说一样……

    https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.6.1

    1. public class Test {
    2. public static void main(String[] args) {
    3. int i=0;
    4. i=i++;
    5. System.out.println(i);
    6. }
    7. }
    8. 结果是0
    9. 为什么是0 因为:
    10. 是因为Java编译器的原因:我们来看看编译后的字节码
      1. 0  iconst_0 //将int型0推送至栈顶
        1  istore_1 //将栈顶int型数值存入第二个本地变量 i=0 (int i=0)
        2  iload_1 //将第二个int型本地变量推送至栈顶  然后将i推送至栈顶 0
        3  iinc 1 1 //将指定int型变量增加指定值(i++, i--, i+=2)完成i++
        4  istore_1 //将栈顶int型数值存入第二个本地变量 将栈元素赋值给了i i=0
        5  getstatic java/lang/System/out Ljava/io/PrintStream;
        6  iload_1

        1. public static void main(String[] args) {
        2. int i=0;
        3. i++;
        4. System.out.println(i);
        5. }
        6. 字节码为:
        7. iconst_0 //将int型0推送至栈顶
        8. istore_1 //将栈顶int型数值存入第二个本地变量 i=0 (int i=0)
        9. iinc 1 1 //将指定int型变量增加指定值(i++, i--, i+=2)完成i++
          1. public static void main(String[] args) {
          2. int i=0;
          3. int k=i++;
          4. System.out.println(i);
          5. }
          6. 字节码为:
          7. 0  iconst_0 //将int型0推送至栈顶
            1  istore_1 //将栈顶int型数值存入第二个本地变量 i=0 (int i=0)
            2  iload_1 //将第二个int型本地变量推送至栈顶  然后将i推送至栈顶 0
            3  iinc 1 1 //将指定int型变量增加指定值(i++, i--, i+=2)完成i++
            4  istore_2 //将栈顶int型数值存入第三个本地变量 将栈元素赋值给了i i=0
            5  getstatic java/lang/System/out Ljava/io/PrintStream;
            6  iload_1
            1. public static void main(String[] args) {
            2. int i=0;
            3. int k=++i;
            4. System.out.println(i);
            5. }
            6. 字节码为:
            7. 0 iconst_0
              1 istore_1
              2 iinc 1 by 1
              5 iload_1
              6 istore_2

      2. 对比而言,对于i++而言,i=i++指令多了两步,2和4
        其实这两步是赋值符号引起的,有意思的是第二步出现的时机,是在iinc之前,这就是因为java lang spec中规定的。
      3. java编译器对于++i 并不会生成2和4
      4. int i=0 i=i++ 或者 int i=0 int j= i++ 相当于 int temp = i i++ i=temp int temp = i i++ j=temp
      5. int i=0 i=++i 相当于 ++i temp = i i=temp;
      6. 总结:java编译器对于i++会先将i的值保存至另一变量 然后在对i++,另一变量仍没有改变。 而对于++i 是先对i++ 然后保存到另一变量 然后赋值。

    http://www.cnblogs.com/eggbucket/archive/2012/05/21/2511926.html

    先看4个题目:

    ①int i = 0;

        i = i++;

    ②int i = 0;

        i = ++i;

    ③int i = 0;

        int j = 0;

        j = i++ + i++;

    ④ int i = 0;

         int j = 0;

         j = i++ + i++ + i++;

    每道题里的i和j都是多少?

    结果分别是
    ①i = 0,
    //出现0可以这样简单理解:自增是单独的一个操作,自增的结果不会体现在变量i上。i++,本身就是逻辑运算(i=i+1)的缩写。
    //容易混淆的地方就在于定义的变量i自增操作的变量i名称相同,如果写成这样i=i;temp=temp+1;的两行代码,就没有疑惑了
    ②i = 1,
    ③i = 2,j = 1,
    ④i = 3,j = 3。

    i++和++i的问题,困扰很多人。现在通过分析字节码,来确定这两条语句究竟是怎样执行的。


    先给出今天要用到的字节码的含义

    Bytecode

    Stack

    before->after

    Description

    iconst_0

    ->0

    Loads the int value 0 onto the stack

    istore_1

    value->

    Store int value into variable 1

    istore_2

    value->

    Store int value into variable 2

    iinc

    No change

    Increment local variable #index by signed byte const

    iload_1

    ->value

    Loads an int value from variable 1

    iadd

    value 1,value 2->result

    Adds 2 ints together

    说明两点需要注意的地方:

    ①iinc操作是有参数的,但是在此忽略,简写为iinc,此操作对应于自加操作,并且该操作不对stack有任何改变;

    ②iadd操作过后只在stack中保留结果result。

    接下来是四段程序主要的字节码:

    ①iconst_0        ②iconst_0        ③iconst_0       ④iconst_0

        istore_1            istore_1            istore_1           istore_1

        iload_1             iinc 1,1              iconst_0          iconst_0

        iinc 1,1             iload_1              istore_2           istore_2

        istore_1            istore_1            iload_1             iload_1

                                                          iinc 1,1             iinc 1,1

                                                          iload_1             iload_1

                                                          iinc 1,1             iinc 1,1

                                                          iadd                  iadd

                                                          istore_2            iload_1

                                                                                   iinc 1,1

                                                                                   iadd

                                                                                   istore_2

    现在解释①。第一步在stack中存入一个int常量0;第二步把它赋值给第一个变量,即我们的i;第三步把第一个变量i的值存入到stack中;第四步在i自身的空间进行自加,而第三步存入到stack中的值没有变;第五步把第三步存入到stack中的值再赋值给第一个变量i。也就是说,i真的是进行自加了,但是被自己原来的值覆盖掉了。从这里我们可以看出,自加操作比赋值操作的优先级高。

    再看②。和①比较起来,区别就在于,++i是先进行自加,然后把自加后的值存入到stack中,所以最后赋值给i的是1,不是0。

    ③呢?首先,③比前两个多了一个变量,但这不是重点。往下看,把第一个变量i的值(0)存入到stack中,i自加,再第一个变量i的值(1)存入到stack中,再自加。此时stack中有两个值了,0和1,进行加操作后的结果是1,然后赋值给第二个变量j。这里颠覆了我们以前被告知的,加的运算优先级比自加高。这就是困扰我们的,其实,只要把值读入到stack中,接下来就进行自加,只有得到了两个参数,才进行加操作。

    来看④。前面都和③一样,只到进行了第一次加操作,得到的结果(1)保存在stack中,没有赋值给j,然后再次读入i的值(2)到stack,i自加,得到加操作的两个参数,1和2,进行第二次加操作的结果是3,赋值给j。

    到此,问题都解决了。

    http://blog.csdn.net/tutuhatec/article/details/6747774

  • 相关阅读:
    SharePoint 2013 通过HttpModule 管理视图权限
    SharePoint 2013 内容部署报错
    SharePoint 2013 日历视图兼容性问题
    SharePoint 2013 JavaScript 对象判断用户权限
    SharePoint 2013 PowerShell命令备份还原报错
    SharePoint 2013 修改表单认证登录页面
    SharePoint 2013 表单认证使用ASP.Net配置工具添加用户
    linux下永久添加静态路由
    CAN协议,系统结构和帧结构
    STM32串口的设置和库函数的介绍
  • 原文地址:https://www.cnblogs.com/softidea/p/4903835.html
Copyright © 2020-2023  润新知