JVM笔记----从JVM角度浅谈 i++ 和 ++i 的区别

我先引入4个问题:

//第一类问题
int i1 = 10;
i1++;

int i2 = 10;
i2++;

//第二类问题
int i3 = 10;
int i4 = i3++;

int i5 = 10;
int i6 = ++i5;

//第三类问题
int i7 = 10;
i7 = i7 ++;

int i8 = 10;
i8 = ++i8;

//第四类问题
int i9 = 10;

int i10 = i9++ + ++i9;

对于i++ 和 ++i相信大家在学习C语言都时候都很好的区别了它们,那这两个玩意在JVM中又是怎么回事呢。

如果不从JVM的角度来看 ++ 运算符相对来说比较简单看,因为只要看运算符优先级就好了。

只从运算符优先级来看的话1-3类问题都是小儿科,也就是第4类问题绕一点。

//第四类问题
int i9 = 10;

int i10 = i9++ + ++i9;

我们来看一下,根据运算符优先级,表达式从左向右运行,首先是 i9++ ,这里 i9 先带入计算 然后自加,然后加上 ++ i9,对于这里是i9先自加再带入运算,

所以就是10 + 12 ,答案就是22,运算结果也是这样的。

但是如果我们要从JVM角度来看的话这个问题应该怎么看呢。

首先我们先从JVM的运行时数据区来看,

我们这里主要看虚拟机栈,不同的线程又有不同的虚拟机栈,由于我们这里只有一个线程,所以线程问题不用考虑了,main函数所在的线程栈只有一个栈帧,

也就是这里的main函数,而栈帧又是由局部变量表,操作数栈,动态链接,方法返回地址,附加信息组成。这里我们只要关心局部变量表和操作数栈就好了。

以下为了方便,我就把局部变量表简称为表,操作数栈简称为栈了。

我们首先来看一下这个程序的字节码:

 0 bipush 10
 2 istore_1
 3 iinc 1 by 1
 6 bipush 10
 8 istore_2
 9 iinc 2 by 1
12 bipush 10
14 istore_3
15 iload_3
16 iinc 3 by 1
19 istore 4
21 bipush 10
23 istore 5
25 iinc 5 by 1
28 iload 5
30 istore 6
32 bipush 10
34 istore 7
36 iload 7
38 iinc 7 by 1
41 istore 7
43 bipush 10
45 istore 8
47 iinc 8 by 1
50 iload 8
52 istore 8
54 bipush 10
56 istore 9
58 iload 9
60 iinc 9 by 1
63 iinc 9 by 1
66 iload 9
68 iadd
69 istore 10
71 return

我们先来关注第一类问题:

int i1 = 10;
i1++;

int i2 = 10;
i2++;

它的字节码是:

0 bipush 10
2 istore_1
3 iinc 1 by 1
6 bipush 10
8 istore_2
9 iinc 2 by 1

它的操作顺序是这样的:

首先把10压入栈中,再把栈顶元素从10存入到表中slot[1]位置 ( 因为slot[0] 是args参数 )

而 iinc 1 by 1 这行代码是指把表中下标为1的数加上1。

所以这就是 int i1 = 10 ; i1 ++ ; 的字节码细节,i2操作同理。

我首先来说明一下这里的常用字节码指令:

iload : 从表 -> 栈
istore : 从栈 -> 表
ipush : 常量 -> 栈
iinc x by n : slot[x](表中下标为x的数据) += n

再来看一下第二类问题:

int i3 = 10;
int i4 = i3++;

int i5 = 10;
int i6 = ++i5;

它的字节码是:

12 bipush 10
14 istore_3
15 iload_3
16 iinc 3 by 1
19 istore 4
21 bipush 10
23 istore 5
25 iinc 5 by 1
28 iload 5
30 istore 6

其中第12 到 19 是前两行操作,先把10压入栈中,在把10弹栈存入表中下标为3的位置,在从表中取出下标为3的数据压入栈中,表中下标为3的数据自加1,然后再把栈顶元素存入到表中下标为4的位置。所以最后i3 = 11,i4 = 10;

而下两行java程序与刚刚这两行唯一的区别就是iload 和 iinc by 的顺序反了。

如果先执行iload则是把表中数据压入栈中,由于我们执行iinc by操作只会对表中数据产生影响,而最终istore操作是把栈中的数存放到表中,所以iinc by操作对我们istore位置的数没有影响。

而后执行iload则不同了,我们先执行iinc by操作先把表中数据自加了,再从表压入栈中,那么最终自加的数据就会保留。

好了,i++ 和 ++i区别的精髓就在这里了,第三类问题和第四类问题交给你们解释了~

 

posted @ 2021-09-22 23:49  Apak陈柏宇  阅读(217)  评论(0编辑  收藏  举报