写在前面
在线编译工具链与反汇编工具: https://godbolt.org/;
使用的编译器: x86_64-clang16.0.
内置类型
i = i + 1
int main() {
int i = 0;
i = i + 1;
}
对应的汇编代码:
mov dword ptr [rbp - 4], 0 ;; i = 0
;;-------------- i = i + 1; -------------------
mov dword ptr [rbp - 8], 0 ;; new_i = 0
mov eax, dword ptr [rbp - 8] ;; eax = new_i
add eax, 1 ;; eax += 1
mov dword ptr [rbp - 8], eax ;; new_i = eax
相当于执行了四个步骤:
- 开辟一个新的内存地址i(不同于之前的i), 记为new_i, 并赋值为0
- 找一个寄存器放置new_i
- eax寄存器执行自增
- new_i赋值为eax
i += 1
int main() {
int i = 0;
i += 1;
}
mov dword ptr [rbp - 4], 0 ;; i = 0
;;--------------- i += 1; -------------------
mov eax, dword ptr [rbp - 4] ;; eax = i
add eax, 1 ;; eax += 1
mov dword ptr [rbp - 4], eax ;; i = eax
三个步骤(少了new_i的操作, 直接在原变量位置操作)
- 存寄存器, 将i的值赋给eax
- eax自增
- 自增后的eax传回i
i++
运算
int main() {
int i = 0;
i++;
}
和++i的没区别.
mov dword ptr [rbp - 4], 0;; i = 0
;;------------ i++; -------------
mov eax, dword ptr [rbp - 4] ;; eax = i
add eax, 1 ;; eax += 1
mov dword ptr [rbp - 4], eax ;; i = eax
赋值
主要讨论赋值的情况.
int main() {
int i = 0;
int c = i++; // c = 0, i = 1
}
mov dword ptr [rbp - 4], 0 ; int i = 0;
;;------------ int c = i++; -------------------
mov eax, dword ptr [rbp - 4] ; eax = i
mov ecx, eax ; ecx = eax
add ecx, 1 ;; ecx += 1
mov dword ptr [rbp - 4], ecx ;; i = ecx
mov dword ptr [rbp - 8], eax ;; c = eax
五个步骤:
- 存寄存器eax: eax中放置i的值
- 新开一个寄存器ecx, 存i的副本
- ecx自增
- ecx赋值给i
- eax赋值给c
++i
运算
int main() {
int i = 0;
++i;
}
和i++的没区别.
mov dword ptr [rbp - 4], 0;; i = 0
;;------------ i++; -------------
mov eax, dword ptr [rbp - 4] ;; eax = i
add eax, 1 ;; eax += 1
mov dword ptr [rbp - 4], eax ;; i = eax
赋值
主要讨论赋值的情况
int main() {
int i = 0;
int d = ++i; // d = 1, i = 1
}
mov dword ptr [rbp - 4], 0 ;; i = 0;
;;------------ int d = ++i; -------------------
mov eax, dword ptr [rbp - 4] ;; eax = i
add eax, 1 ;; eax += 1
mov dword ptr [rbp - 4], eax ;; i = eax, 相当于对原对象自增
mov dword ptr [rbp - 8], eax ;; d = eax
四个步骤:
- 寄存器eax存i的值
- eax自增
- eax的值赋给i
- eax的值赋给d
自定义类型
class I {
int x;
public:
I() : x(0) {}
I(int _x) : x(_x) {}
I operator+(const I& rhs) {
// 返回临时对象, 如果返回引用,
// 则会报错(返回的实际上是被销毁的局部对象)
I tmp;
tmp += rhs; // 调用operator+=
return tmp;
}
I& operator+=(I inc) {
this->x += inc.x;
return *this;
}
I& operator++() { // 传引用使之可以链式调用
++this->x;
return *this;
}
I operator++(int) {
I tmp(*this);
++*this; // 调用operator++(int)
return tmp;
}
};
反汇编:
i++
int main() {
I i;
i++;
}
下面的反汇编代码仅针对i++;
这一行.
lea rdi, [rbp - 8] ;; 读取i的地址(lea和mov的区别)
xor esi, esi ;; esi置为0
call I::operator++(int) ;; 调用后置++
mov dword ptr [rbp - 16], eax ;; eax的值赋给i
下面针对后置++反汇编:
I::operator++(int): # @I::operator++(int)
push rbp
mov rbp, rsp
sub rsp, 32
mov qword ptr [rbp - 16], rdi
mov dword ptr [rbp - 20], esi
mov rdi, qword ptr [rbp - 16]
mov eax, dword ptr [rdi]
mov dword ptr [rbp - 8], eax
call I::operator++()
mov eax, dword ptr [rbp - 8]
add rsp, 32
pop rbp
ret
总结
效率方面: (汇编代码很容易看出, 每一次开辟新变量以及每一次赋值都要花费CPU时间)
i = i + 1
< i += 1
< i++
< ++i
这是针对内置类型的情况, 实际自定义类型的话, 还要考虑对象的拷贝和移动等操作.