c++ RVO返回值优化
RVO
gcc、clang默认开启,MSVC默认关闭。可以通过编译选项-fno-elide-constructors
关闭优化。
标准布局类型
关闭返回值优化
class Object {
public:
Object() {
printf("Default constructor\n");
a = b = c = d = 0;
}
int a;
int b;
int c;
int d;
};
Object foo() {
Object p;
p.a = 1;
p.b = 2;
p.c = 3;
p.d = 4;
return p;
}
int main() {
Object obj = foo();
return 0;
}
foo():
push rbp
mov rbp, rsp
sub rsp, 32
lea rax, [rbp-32]
mov rdi, rax
call Object::Object() [complete object constructor]
mov DWORD PTR [rbp-32], 1
mov DWORD PTR [rbp-28], 2
mov DWORD PTR [rbp-24], 3
mov DWORD PTR [rbp-20], 4
lea rdx, [rbp-32]
lea rax, [rbp-16]
mov rsi, rdx
mov rdi, rax
call Object::Object(Object&&) [complete object constructor]
mov rax, QWORD PTR [rbp-16]
mov rdx, QWORD PTR [rbp-8]
leave
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
call foo()
mov QWORD PTR [rbp-16], rax
mov QWORD PTR [rbp-8], rdx
mov eax, 0
leave
ret
当POD类不提供拷贝构造函数,且关闭RVO返回值优化时,由编译器自动生成拷贝构造函数。
==此时19行 return p 时,发生了拷贝构造==。
开启返回值优化
1. 无定义拷贝构造函数
class Object {
public:
Object() {
printf("Default constructor\n");
a = b = c = d = 0;
}
int a;
int b;
int c;
int d;
};
Object foo() {
Object p;
p.a = 1;
p.b = 2;
p.c = 3;
p.d = 4;
return p;
}
int main() {
Object obj = foo();
return 0;
}
foo():
push rbp
mov rbp, rsp
sub rsp, 16
lea rax, [rbp-16]
mov rdi, rax
call Object::Object() [complete object constructor]
mov DWORD PTR [rbp-16], 1
mov DWORD PTR [rbp-12], 2
mov DWORD PTR [rbp-8], 3
mov DWORD PTR [rbp-4], 4
mov rax, QWORD PTR [rbp-16]
mov rdx, QWORD PTR [rbp-8]
leave
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
call foo()
mov QWORD PTR [rbp-16], rax
mov QWORD PTR [rbp-8], rdx
mov eax, 0
leave
ret
汇编第6,7行构造了Object类的对象,8-11行对成员进行赋值。
==通过将对象的值拷贝到rax和rdx寄存器中作为返回值返回==
2. 定义了拷贝构造
#include <cstdio>
#include <string.h>
class Object {
public:
Object() {
printf("Default constructor\n");
a = b = c = d = 0;
}
Object(const Object& rhs) {
printf("Copy constructor\n");
memcpy(this, &rhs, sizeof(Object));
}
int a;
int b;
int c;
int d;
};
Object foo() {
Object p;
p.a = 1;
p.b = 2;
p.c = 3;
p.d = 4;
return p;
}
int main() {
Object obj = foo();
return 0;
}
foo():
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov rdi, rax
call Object::Object() [complete object constructor]
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], 1
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax+4], 2
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax+8], 3
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax+12], 4
nop
mov rax, QWORD PTR [rbp-8]
leave
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
lea rax, [rbp-16]
mov rdi, rax
call foo()
mov eax, 0
leave
ret
可以看到汇编代码中没有生成拷贝构造函数的代码,被编译器所优化。
但是此时汇编代码与情况1有所不同
-
sub rsp, 16 -> lea rax, [rbp-16]
预留Object
对象所需的16字节空间,将栈顶的内容加载到寄存器rax
中 -
汇编第26行
mov rdi, rax
将一个对象的地址加载到rdi
中。 -
第27行并未调用构造函数,而是将对象的地址传入
foo
函数中,在foo
函数中调用构造函数 -
mov rax, QWORD PTR [rbp-8]
此时rax值为main作用域下为对象预留空间的底部地址 -
10-16行对对象的成员进行赋值
以上流程类似于下面所示代码
Object obj = foo();
// 将被改成:
Object obj; // 这里不需要调用默认构造函数
foo(obj);
// 相应地foo函数将被改写定义:
void foo(Object& obj) {
obj.Object::Object(); // 调用Object的默认构造函数
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
return;
}
若用户自定义了拷贝构造,说明是要进行拷贝大块的内存空间等之类的操作,不仅仅是普通的数据成员的拷贝。如果拷贝构造函数中只是拷贝数据成员,可以不必定义拷贝构造函数,编译器会采用更高效的逐成员拷贝的方法,编译器内部就可以帮程序员做好了,所以有拷贝构造函数的存在就说明有需要低效的拷贝动作,那么就要想办法消除掉拷贝的操作,那么启用NRV优化就是一项提高效率的做法了。