问题提出

上周上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_0value ->-> 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 的顺序不同。

indexi++局部变量++i局部变量
1iconst_00nulliconst_00null
2istore_0null0istore_0null0
3iload_000iinc 0 1null1
4iinc 0 101iload_011
5istore_0null0istore_0null1
6iload_000iload_011
8ireturnnull0ireturnnull1

相信能够发现区别,于是我们可以做出推断

  • 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 的区别和联系 ,了解这种运算是如何进行的。 另外,由于本人水平有限,难免存在错误。欢迎评论反馈。

注释


  1. 局部变量表的编号从0开始。
  2. 局部变量表可以javap的-v参数也可以生成。 这里采用Bytecode Viewer 生成的结果。
最后修改:2020 年 04 月 22 日
如果觉得我的文章对你有用,请随意赞赏