++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
4int 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
4int 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
10int 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;