问题引出
昨天下午,和一个朋友聊找工作需要复习的知识点的时候,他突然问了我这么一道程序题。
1 | int i = 0; |
于是想了想便回答i = 1,但是没想到朋友说答案是i = 0。似乎有些出乎意料,印象里,i++是先将i的值赋值于指定变量后再做自增的。这里的代码不是应该是类似于
1 | i=i; |
如果是这样的话那i的值应该为1才是呀!疑惑不解!!!随后朋友便发来了几张程序运行图为他的答案做出了有力的支撑。
- 当i = i++时
- 当i = i++ + i++时
- 当i = i++ + i++ + i++时
看到了这些运行结果的截图,也不得不相信,但是心里还是充满了疑惑。
寻根问底
对于这种现象,首先想到是不是i++与赋值操作是同时执行的?又或者,虚拟机在实现这类计算的时候是不是做了什么神奇的操作?怀抱着疑惑与求之的心态,开始了探索之旅。
- i++与赋值操作是同时执行?
通过多次的运算后排除了这种可能性。
- 虚拟机是怎么实现这类运算的?
首先想到了Android上常见到的smali语言的虚拟机指令,所以百度了一下java相关的虚拟机指令的文章,看到了通过javap命令可以反汇编输出java虚拟机的字节码指令,于是考虑用这种方法进一步尝试。
着手实验
- 测试代码
首先写了一个简单的java测试代码,代码里面的main()方法就只有两行简单的语句。
1 | public class Test { |
- 反汇编java字节码文件
写好了demo之后,通过命令行窗口进行下一步操作。如图所示,通过javap命令输出反汇编后的文件内容,得到了java虚拟机的字节码指令。
- 理解指令处理流程
虽然以前有看过smali语法和一些简单的汇编语法,但是看到这个java的字节码指令后完全懵逼了。这些指令是什么意思都不知道,更别提阅读了。通过一番的搜索,查找到相关资料后,得到了一篇还比较全面的指令解释的文章。
https://www.cnblogs.com/tenghoo/p/jvm_opcodejvm.html。
根据文章里的内容整理出了反汇编后出现的几个指令的意思。
1 | iconst_n 将int型(n)推送至栈顶 |
顺着上面的这几条指令看,慢慢的揭开i=i++,最后i=0的神秘面纱了。
- 第一步指令:iconst_0
推送int型的数值0到栈顶中。
- 第二步指令:istore_1
将栈顶int型数值存入到第2个本地变量
- 第三步指令:iload_1
将第2个int型的本地变量推送至栈顶
- 第四步指令:iinc 1, 1
将第2个本地变量自增1
- 第五步指令:istore_1
将栈顶int型数值存入到第2个本地变量
根据以上的步骤可知此时的第2个本地变量的值应该是0,而i就是这第二个本地变量,因此i = 0。
后话
很多事情都没有想象中那么简单,有时候觉得理所当然的事情,可能因为存在本质上的差别和个体上的差异而导致了不相同的结果,而此时,应该怀揣着求之与探索的精神,去追寻其根源,从而才能够从根本上去解决问题。
补充说明
当执行了istore_n指令之后其实栈中的值应该是要弹出的,但是画图的时候没画对,现在也不想重新再画了,所以这一点需要注意一下,在这里做补充说明。