直接讲结论,在gcc 10.2下,关闭优化-O0
的情况下,编译器同样会启用RVO 和 NRVO
。然而,再通过-fno-elide-constructors
禁用RVO之后,编译器如果发现存在移动构造函数,会优先使用移动构造而不是拷贝构造。
cmake_minimum_required(VERSION 3.24)
project(rvo_testing)
add_executable(
exec_with_fno_elide
main.cpp
)
set_target_properties(exec_with_fno_elide
PROPERTIES
COMPILE_FLAGS
"-fno-elide-constructors -O0"
)
add_executable(
exec_default
main.cpp
)
set_target_properties(exec_default
PROPERTIES
COMPILE_FLAGS
"-O0"
)
#include <iostream>
using namespace std;
// static const char __func__[] = "function-name";
class Foo{
public:
Foo(){
// gnu c extension
cout << __PRETTY_FUNCTION__ << endl;
}
Foo(const Foo&){
cout << __PRETTY_FUNCTION__ << endl;
}
Foo(const Foo&&){
cout << __PRETTY_FUNCTION__ << endl;
}
~Foo(){
cout << __PRETTY_FUNCTION__ << endl;
}
};
// NRVO
Foo ReturnLocalObjWithNamed(){
Foo foo;
return foo;
}
// RVO
Foo ReturnLocalObjWithAnon(){
// Foo foo;
return Foo();
}
// without RVO
Foo ReturnLocalObjWithMove(){
// Foo foo;
return std::move(Foo());
}
int main(){
// buf ==> convert to Foo pointer
{
Foo obj = ReturnLocalObjWithNamed();
}
cout << "===============" << endl;
{
Foo obj = ReturnLocalObjWithAnon();
}
cout << "===============" << endl;
{
Foo obj = ReturnLocalObjWithMove();
}
return 0;
}
底层原理
本质上相当于,在main函数之前分配一块内存存放Foo
对象,传递对象指针给调用函数。
同时在调用函数内部使用placement new
初始化Foo
对象
ReturnLocalObjWithNamed():
push rbp
mov rbp,rsp
sub rsp,0x10
mov QWORD PTR [rbp-0x8],rdi // 64位系统下, 指针占用 8bytes
mov rax,QWORD PTR [rbp-0x8]
mov rdi,rax
call 401320 <Foo::Foo()> // Foo构造函数
nop
mov rax,QWORD PTR [rbp-0x8]
leave
ret
这个是main
函数调用的ReturnLocalObjWithNamed
的汇编代码
push rbp
mov rbp,rsp
sub rsp,0x10 // 栈上分配0x10字节
lea rax,[rbp-0x1] // 注意Foo是空类, 这里描述的是分配了一个Foo对象
mov rdi,rax // rdi: Foo对象指针
call 401196 <ReturnLocalObjWithNamed()>
lea rax,[rbp-0x1]
mov rdi,rax
call 40137c <Foo::~Foo()>
相当于直接在main
函数那里,分配内存,在ReturnLocalObjWithNamed
中进行初始化…