「探索发现」通过反汇编Java字节码文件输出字节码指令探索有关i++的问题

问题引出

  昨天下午,和一个朋友聊找工作需要复习的知识点的时候,他突然问了我这么一道程序题。

1
2
3
int i = 0;
i = i++;
最后i是多少?

  于是想了想便回答i = 1,但是没想到朋友说答案是i = 0。似乎有些出乎意料,印象里,i++是先将i的值赋值于指定变量后再做自增的。这里的代码不是应该是类似于

1
2
i=i;
i=i+1;

  如果是这样的话那i的值应该为1才是呀!疑惑不解!!!随后朋友便发来了几张程序运行图为他的答案做出了有力的支撑。

  • 当i = i++时

当i = i++时

  • 当i = i++ + i++时

当i = i++ + i++时

  • 当i = i++ + i++ + i++时

当i = i++ + i++ + i++时

  看到了这些运行结果的截图,也不得不相信,但是心里还是充满了疑惑。

寻根问底

  对于这种现象,首先想到是不是i++与赋值操作是同时执行的?又或者,虚拟机在实现这类计算的时候是不是做了什么神奇的操作?怀抱着疑惑与求之的心态,开始了探索之旅。

  • i++与赋值操作是同时执行?

  通过多次的运算后排除了这种可能性。

  • 虚拟机是怎么实现这类运算的?

  首先想到了Android上常见到的smali语言的虚拟机指令,所以百度了一下java相关的虚拟机指令的文章,看到了通过javap命令可以反汇编输出java虚拟机的字节码指令,于是考虑用这种方法进一步尝试。

着手实验

  • 测试代码

  首先写了一个简单的java测试代码,代码里面的main()方法就只有两行简单的语句。

1
2
3
4
5
6
public class Test {
public static void main(String[] args) {
int i = 0;
i = i++;
}
}
  • 反汇编java字节码文件

  写好了demo之后,通过命令行窗口进行下一步操作。如图所示,通过javap命令输出反汇编后的文件内容,得到了java虚拟机的字节码指令。

反汇编java字节码文件

  • 理解指令处理流程

  虽然以前有看过smali语法和一些简单的汇编语法,但是看到这个java的字节码指令后完全懵逼了。这些指令是什么意思都不知道,更别提阅读了。通过一番的搜索,查找到相关资料后,得到了一篇还比较全面的指令解释的文章。
  https://www.cnblogs.com/tenghoo/p/jvm_opcodejvm.html
  根据文章里的内容整理出了反汇编后出现的几个指令的意思。

1
2
3
4
iconst_n                将int型(n)推送至栈顶
istore_n 将栈顶int型数值存入第n+1个本地变量
iload_n 将第n+1个int型本地变量推送至栈顶
iinc n, m 局部变量自增指令,将第n+1个本地变量自增m

  顺着上面的这几条指令看,慢慢的揭开i=i++,最后i=0的神秘面纱了。

  • 第一步指令:iconst_0

  推送int型的数值0到栈顶中。

推送int型的数值0到栈顶中。

  • 第二步指令:istore_1

  将栈顶int型数值存入到第2个本地变量

将栈顶int型数值存入到第2个本地变量

  • 第三步指令:iload_1

  将第2个int型的本地变量推送至栈顶

将第2个int型的本地变量推送至栈顶

  • 第四步指令:iinc 1, 1

  将第2个本地变量自增1

将第2个本地变量自增1

  • 第五步指令:istore_1

  将栈顶int型数值存入到第2个本地变量

将栈顶int型数值存入到第2个本地变量

  根据以上的步骤可知此时的第2个本地变量的值应该是0,而i就是这第二个本地变量,因此i = 0。

后话

  很多事情都没有想象中那么简单,有时候觉得理所当然的事情,可能因为存在本质上的差别和个体上的差异而导致了不相同的结果,而此时,应该怀揣着求之与探索的精神,去追寻其根源,从而才能够从根本上去解决问题。

补充说明

  当执行了istore_n指令之后其实栈中的值应该是要弹出的,但是画图的时候没画对,现在也不想重新再画了,所以这一点需要注意一下,在这里做补充说明。