汇编角度看值传递类对象
首先奉上源代码
class A{
private:
int i;
public:
A() {
std::cout << "default constructor is called" << std::endl;
i = 0;
}
A(int _i): i(_i) {}
A(const A& obj) {
std::cout << "copy constructor is called" << std::endl;
i = obj.i;
}
A& operator = (const A& obj) {
std::cout << "assignment constructor is called" << std::endl;
i = obj.i;
return *this;
}
A (const std::initializer_list<int>& l) {
std::cout << "initializer_list constructor is called" << std::endl;
i = *(l.begin());
}
int get()const {
return i;
}
void set(int j) {
i = j;
}
};
void foo(A a){
a.set(0xFF);
}
int main() {
A a(3);
foo(a);
return 0;
}
对于main函数中foo(a)这个函数调用,生成的汇编如下:
45 foo(a);
0x00005562b9bc5219 <+44>: lea rdx,[rbp-0x10]
0x00005562b9bc521d <+48>: lea rax,[rbp-0xc]
0x00005562b9bc5221 <+52>: mov rsi,rdx
0x00005562b9bc5224 <+55>: mov rdi,rax
0x00005562b9bc5227 <+58>: call 0x5562b9bc52d6 <A::A(A const&)>
0x00005562b9bc522c <+63>: lea rax,[rbp-0xc]
0x00005562b9bc5230 <+67>: mov rdi,rax
0x00005562b9bc5233 <+70>: call 0x5562b9bc51c9 <foo(A)>
从以上汇编可以明显看到,如果foo()的形参是一个值类型的话,首先调用copy构造函数生成一个实参对象的副本,然后把这个副本的地址传递给函数调用,记住,是副本的地址!
以下是foo的汇编,
Dump of assembler code for function foo(A):
39 void foo(A a){
0x00005562b9bc51c9 <+0>: endbr64
0x00005562b9bc51cd <+4>: push rbp
0x00005562b9bc51ce <+5>: mov rbp,rsp
0x00005562b9bc51d1 <+8>: sub rsp,0x10
0x00005562b9bc51d5 <+12>: mov QWORD PTR [rbp-0x8],rdi
40 a.set(0xFF);
0x00005562b9bc51d9 <+16>: mov rax,QWORD PTR [rbp-0x8]
0x00005562b9bc51dd <+20>: mov esi,0xff
=> 0x00005562b9bc51e2 <+25>: mov rdi,rax
0x00005562b9bc51e5 <+28>: call 0x5562b9bc5322 <A::set(int)>
41 }
0x00005562b9bc51ea <+33>: nop
0x00005562b9bc51eb <+34>: leave
0x00005562b9bc51ec <+35>: ret
End of assembler dump.
其实也很好理解,如果要原模原样的copy一个类对象的值的话,效率是非常低下的, 也非常浪费栈空间。