++i和i++的区别

简述

二者的区别本质上在于++i属于左值操作,而i++属于右值操作,可分以下几种情况分析:

  • 只有自增操作

    如果只用于自增操作,++i和i++经过编译器优化之后其实是等价的,以下是二者的汇编代码(编译器为x86-64 gcc 11.2):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // 源代码
    void f1(int i) {
    ++i;
    }

    void f2(int i) {
    i++;
    }

    // 汇编代码
    f1(int):
    push rbp
    mov rbp, rsp
    mov DWORD PTR [rbp-4], edi // 形参i赋值

    add DWORD PTR [rbp-4], 1 // ++i

    nop
    pop rbp
    ret
    f2(int):
    push rbp
    mov rbp, rsp
    mov DWORD PTR [rbp-4], edi // 形参i赋值

    add DWORD PTR [rbp-4], 1 // i++

    nop
    pop rbp
    ret

    此处,i++被编译优化后变成了左值操作,因此二者的汇编代码相同。那么在这种情况下它们的执行速度也是一样的。

  • 自增后赋值

    如果自增后再赋值,编译器将严格安装二者的左右值属性进行操作,比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 源代码
    void f1(int i) {
    i = ++i;
    }

    void f2(int i) {
    i = i++;
    }

    // 汇编代码
    f1(int):
    push rbp
    mov rbp, rsp
    mov DWORD PTR [rbp-4], edi

    add DWORD PTR [rbp-4], 1 // ++i仍使用一行汇编指令即可完成自增和赋值

    nop
    pop rbp
    ret
    f2(int):
    push rbp
    mov rbp, rsp
    mov DWORD PTR [rbp-4], edi
    // i++则会被拆分成3条汇编指令:
    mov eax, DWORD PTR [rbp-4] // 右值操作:读取i值,存入临时变量中,即寄存器eax
    lea edx, [rax+1] // 寄存器自增加1
    mov DWORD PTR [rbp-4], edx // 将自增后的右值赋值给形参i(i是左值)

    mov DWORD PTR [rbp-4], eax
    nop
    pop rbp
    ret

    由于i++对应三条汇编指令,而++i只有一条汇编指令,因此这种情况下,++i的执行速度会快于

    i++。

    此外,由于i++会被拆分成3条汇编指令,因此在未加锁的多线程环境下,三条指令执行中可能会因为线程切换而出现中断,进而导致计算结果未知。而由于++i只有一条汇编指令,因++i还可避免因为i++引入的竞争问题。

扩展

  • 计算 i++ + i++

    1
    2
    3
    4
    int i = 1;
    i++ + i++ = 3; // 从左往右:先执行第一个i++,返回值为字面量1,此时i = 1 + 1 = 2,
    // 再执行第二个i++,返回值为字面量2,此时i = 2 + 1 = 3,
    // 最后两个返回值相加,1 + 2 = 3
  • 计算 ++i + i++ 和 i++ + ++i

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // ++i + i++ 
    int i = 1;
    ++i + i++ = 5; // 从左往右:先执行++i,返回值是i的引用r,此时i自增一次值为2
    // 再执行i++,返回值为字面量2,此时i经过两次自增后值为3
    // 最后两个返回值相加,r + 2 = 3 + 2 = 5

    // i++ + ++i
    int i = 1;
    i++ + ++i = 3; // 从左往右:先执行i++,返回值为字面量1,此时i = 2,
    // 在执行++i,返回值为i的引用r,此时i经过两次自增后值为3,
    // 最后两个返回值相加:1 + r = 1 + 3 = 4
  • 计算 ++i + ++i

    1
    2
    3
    4
    int i = 1;
    ++i + ++i = 6; // 从左往右:先执行第一个++i,返回值是i的引用r1,
    // 再执行第二个++i,返回值为i的引用r2,此时i经过两次自增后值为3,
    // 最后两个返回值相加,3 + 3 = 6
  • 计算 ++i + ++i + ++i

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int i = 1;
    ++i + ++i + ++i = 10; // 从左往右:先执行第一个++i,返回值是i的引用r1,
    // 再执行第二个++i,返回值是i的引用r2,此时i经过两次自增后值为3,
    // 此时对两个返回值相加,返回值为字面量a:r1 + r2 = 6,
    // 然后执行第三个++i,返回值是i的引用r3,此时i经过三次自增后值为4,
    // 最后执行第二个加法:a + r3 = 6 + 4 = 10

    // 同理可得
    int i = 1;
    i += ++i + ++i + ++i = 4 + 10 = 14;

Comments