实例观察c++返回值优化
如下代码,在 MakeObj() 中创建了一个局部对象 obj,并将 obj 返回。Test() 函数调用了 MakeObj(),并将 MakeObj() 的返回值赋值给了 obj。按我们的预期,MakeObj() 是值返回,在 main() 调用 Test() 的过程中,应该发生了两次构造和两次析构。:默认构造,在 MakeObj() 声明局部变量的时候。拷贝构造,在 MakeObj() 返回的
1 返回值优化现象 RVO
如下代码,在MakeObj中创建了一个局部对象obj,并将obj返回。 Test函数调用了MakeObj,并将 MakeObj的返回值赋值给了obj。
按我们的预期,MakeObj是值返回,在main调用Test的过程中,应该发生了两次构造和两次析构。
第一次构造:默认构造,在MakeObj声明局部变量的时候。
第二次构造:拷贝构造,在MakeObj返回的时候,将返回值赋值给Test中的局部变量obj时
第一次析构:在函数Test中,MakeObj返回值赋值给Test中的obj完成时, MakeObj中的局部变量析构
第二次析构:在函数Test返回时,Test函数中的obj析构
#include <iostream>
class Obj {
public:
Obj(int i) {
i_ = i;
std::cout << "Obj(), i: " << i_ << ", this: " << this << std::endl;
}
Obj(const Obj &obj) {
i_ = obj.i_;
std::cout << "copy constructor, i: " << i_ << ", this: " << this << std::endl;
}
~Obj() {
std::cout << "~Obj(), i: " << i_ << ", this: " << this << std::endl;
}
int i_;
};
Obj MakeObj() {
int a = 10;
Obj obj(1);
int b = 10;
printf("1, &obj = %p, &a = %p, &b = %p, sizeof(obj) = %d\n", &obj, &a, &b, sizeof(obj));
return obj;
}
void Test() {
printf("before make obj\n");
int a = 100;
Obj obj = MakeObj();
int b = 100;
printf("2, &obj = %p, &a = %p, &b = %p\n", &obj, &a, &b);
printf("after make obj\n");
}
int main() {
Test();
printf("before main return\n");
return 0;
}
编译之后运行,现象与我们预期的并不是一致的。如下图所示,只发生了一次构造和一次析构。
并且在MakeObj和Test中均把obj的地址打印出来了,两个对象的地址是一样的。从obj和a、b地址的相对关系也能看出来,obj创建在了函数Test的栈上,而不是MakeObj的栈上。
2 禁用返回值优化:-fno-elide-constructors
g++ 编译的时候,默认开启了返回值优化,如果想要禁用返回值优化,那么在编译时需要带上标志 -fno-elide-constructors。
如下图所示,禁用返回值优化之后,运行过程与我们预期的是一致的。
3 局部变量在函数返回的时候没有销毁吗
从上边的实验可以看出,默认情况下,编译器开启了返回值优化。但是从常识来看,函数返回的时候,局部变量(栈空间)要释放,对象会销毁。
那么开启返回值优化的时候,函数返时,难道局部对象没有销毁 ?
开启返回值优化的时候,并没有违反栈销毁的规律。
从上边的打印结果可以看出来,在函数MakeObj以及Test函数中,局部变量obj前后分别定义了局部变量a和b,把a和b的地址也打印出来了。
局部变量都是保存在栈上,一个函数中的多个局部变量的地址应该是相邻的才对。但是看打印结果,obj的地址和MakeObj中的a和b的地址并不是相邻的,而是和Test中的a和b是相邻的。这就是编译器起的作用,编译器判断变量的生命周期,然后决定将局部变量放在哪个位置。
返回值优化,优化了性能,但是打乱了对局部变量所在栈的位置的理解。
更多推荐
所有评论(0)