Mas0n
to be reverse engineer🐧
翻车鱼

Java中的i=i++

地点:某群,事件:某群友发了一张图,大致就是问int i =1; i = i++; i 的值是多少

我说简单啊,不就是i = 2吗?结果一到Java中运行,光速打脸 – -|

展现了我的愚昧无知之后,立马百度科普一下:JVM中的局部变量自增。

然而我不了解JVM外加有点蠢,还是不理解… 于是,自己编译了一个样本,来分析 jvm bytecode

“鲜明”对比

public class Main {

    public static void main(String[] args) {
        int i = 1;
        i = i++;
        System.out.println(i);
    }
}

javap -c xx.class,打印出jvm的字节码

https://cdn.shi1011.cn/2021/04/f3e22213b206cdae607a269fe8affde3.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

百度了查了查jvm字节码指令表,写了份分析注释

public class com.helloworld.Main {
  public com.helloworld.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1                  // 1 压入操作数栈帧顶
       1: istore_1                  // 取操作数栈帧顶数据(1) 压入 局部变量表索引为1的变量中
       2: iload_1                   // 取局部变量表索引为1的变量, 压入操作数栈帧顶
       3: iinc          1, 1        // 取局部变量表索引为1的变量 进行自增操作(++),这里需要注意的是iinc指令直接对局部变量进行操作
       6: istore_1                  // 取操作数栈帧顶数据(1), 压入局部变量表索引为1的变量中
								// 
       7: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: iload_1
      11: invokevirtual #13                 // Method java/io/PrintStream.println:(I)V
      14: return
}

原因明显是iinc操作的是变量本身,并没有入栈(操作数栈),于是在赋值时的值依旧是1,那么为什么没有入栈呢?

下面来看看Java中的最简单的自增运算和赋值运算

https://cdn.shi1011.cn/2021/05/72b5ee11845d4124695b3c14087498ac.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5
最简单的自增运算 i++;
https://cdn.shi1011.cn/2021/05/2cec056acf57000d2cc4d4a9a86d84d5.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5
赋值运算 i = i + 1

对比字节码后,可以发现两者不同点在于:Jvm中iinc指令操作的直接是局部变量本身,而Jvm的赋值是入栈->操作->出栈

根据运算规则,可以知道Java首先执行自增(i++),而后进行赋值,但由于自增并不是基于操作数栈的操作,在自增执行完成后,此时的操作数栈顶数据依旧是变量入栈时的数据(1),所以i = i++恒等于原先的值(1)

延展

那么在以C为代表的非虚拟机运行的语言中,自增与赋值又是怎么样的一种表现?简单分析一下C的汇编

https://cdn.shi1011.cn/2021/04/5576107a5879cb50ef3923671c63312d.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

发现不管是自增运算或是赋值运算,C都是利用寄存器实现(至于i++与i=i+1为什么没有区别,应该是编译器优化掉了)

END

某群

没学过Java的我:!!!Java这么骚?学到了学到了…

某群内大佬:….这难道不是基础?

参考资料

这一次,彻底解决Java的值传递和引用传递 (juejin.cn)

本文链接:https://blog.shi1011.cn/learn/1392
本文采用 CC BY-NC-SA 4.0 Unported 协议进行许可

Mas0n

文章作者

发表回复

textsms
account_circle
email

翻车鱼

Java中的i=i++
地点:某群,事件:某群友发了一张图,大致就是问int i =1; i = i++; 中 i 的值是多少 我说简单啊,不就是i = 2吗?结果一到Java中运行,光速打脸 - -| 展现了我的愚昧无知之后,立…
扫描二维码继续阅读
2021-05-01