问题提出
上周上C++程序设计课的时候有同学提出了一个问题,下面这个程序
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
int i = 0;
i = i++;
cout << i << endl;
return 0;
}
输出值是0而不是1。输出值应该为0的理由是 i++ 是先赋值后__自增__,也就是相等于下面这段代码
i = i; // i=0
i++; // i=1
但我认为,i++应该相等于一个函数,代码如下:
int plus(int i) {
int var = i;
i = i + 1;
return var;
}
也就是说,尽管变量i(0)自增变成i(1)了,但是随后又把赋值把i++的返回值0赋给i,所以i的最终的值还是0。
课后我希望能了解i++的具体实现,考虑到目前并没有汇编语言的知识,且Java实现上述代码和C++结果相同,所以我用Java进行了以下实验。
实验环境
系统:Windows 10 x64
JDK版本:Oracle JDK 8u241
编译命令:javac ./Main.java
实验代码
下面我尝试比较 i = i++ 和 i = ++i 的字节码指令。
public class Main {
public static void main(String[] args) {
System.out.println(i1());
System.out.println(i2());
}
public static int i1() {
int i = 0;
i = i++;
return i;
}
public static int i2() {
int i = 0;
i = ++i;
return i;
}
}
输出结果
0
1
下面用到的指令简单解释
字节码指令 | 栈的变化 | 局部变量表的变化 | 指令描述 |
---|---|---|---|
iconst_0 | -> 0 | 无变化 | 将int类型0压入栈 |
istore_0 | value -> | -> value | 将int类型弹出栈,存入第01个局部变量 |
iinc 0 1 | 无变化 | value -> value + 1 | 给第0个局部变量加上1 |
iload_0 | -> value | 无变化 | 将int类型的第0个局部变量压入栈 |
ireturn | -> value | 无变化 | 返回结果 |
然后后javap -c /.Main.class 2查看字节码
i++
public static int i1();
<localVar:index=0 , name=i , desc=I, sig=null, start=L1, end=L2>
Code:
0: iconst_0
1: istore_0
2: iload_0
3: iinc 0, 1
6: istore_0
7: iload_0
8: ireturn
}
++i
public static int i2();
<localVar:index=0 , name=i , desc=I, sig=null, start=L1, end=L2>
Code:
0: iconst_0
1: istore_0
2: iinc 0, 1
5: iload_0
6: istore_0
7: iload_0
8: ireturn
}
经过比较我们可以发现,i++ 和 ++i 仅是 iload 和 iinc 的顺序不同。
index | i++ | 栈 | 局部变量 | ++i | 栈 | 局部变量 |
---|---|---|---|---|---|---|
1 | iconst_0 | 0 | null | iconst_0 | 0 | null |
2 | istore_0 | null | 0 | istore_0 | null | 0 |
3 | iload_0 | 0 | 0 | iinc 0 1 | null | 1 |
4 | iinc 0 1 | 0 | 1 | iload_0 | 1 | 1 |
5 | istore_0 | null | 0 | istore_0 | null | 1 |
6 | iload_0 | 0 | 0 | iload_0 | 1 | 1 |
8 | ireturn | null | 0 | ireturn | null | 1 |
相信能够发现区别,于是我们可以做出推断
- Java自增操作不需要入栈,可以直接修改局部变量表。
- 当写下赋值号右边的 i 的时,相当于要求局部变量 i 入栈,赋值号左边的 i,要求弹出栈中变量,赋值。(所以写下i = i++ 时编译器有一个warning: The assignment to variable i has no effect)
拓展
解释执行完代码后,j的值。
代码 | 结果 |
---|---|
int i = 0; int j = i++; | 0 |
int i = 0: int j = ++i; | 1 |
int i = 0; int j = i++ + ++i; | 2 |
int i = 0; int j = ++i + i++; | 2 |
int j = 0; j = j++ + ++j; | 2 |
int j = 0; j = ++j + j++; | 2 |
int i = 0; int j = i++ + i++ + i++; | 3 |
int i = 0; int j = ++i + ++i + i++; | 5 |
结语
尽管实际生产中我们不大可能写出这种“难以理解”,但这样研究,使我深入理解了 i++ 和 ++i 的区别和联系 ,了解这种运算是如何进行的。 另外,由于本人水平有限,难免存在错误。欢迎评论反馈。
注释