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是相邻的。这就是编译器起的作用,编译器判断变量的生命周期,然后决定将局部变量放在哪个位置。

返回值优化,优化了性能,但是打乱了对局部变量所在栈的位置的理解。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐