为什么c ++中的函数可以返回堆栈值

时间:2016-07-05 21:33:38

标签: c++

考虑以下计划:

class C {
  ...
};

const C f() {
    C ret;
    cout << &ret << endl;
    return ret;
}

int main() {
    C value = f();
    cout << &value << endl;
}


result: // note the address are the same
0x7ffdd24b26e0
0x7ffdd24b26e0

函数f()和变量'value'中的变量'ret'具有相同的内存地址,因此看起来'value'不是'ret'的副本。变量'ret'是一个堆栈变量,因此在f()返回后它应该无效。那么为什么c ++允许在函数内返回堆栈值?

g ++版本:

g++ (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4

4 个答案:

答案 0 :(得分:4)

ret和地址相同的原因是所谓的返回值优化(RVO)。这意味着在这种情况下不会执行副本。但请注意,您不能依赖于此,因为它不一定会发生(尽管这会随着C ++ 17 [至少是当前的草案]而改变)。

https://en.wikipedia.org/wiki/Return_value_optimization

答案 1 :(得分:3)

这是copy-elision的一个例子,特别是RVO(返回值优化)。它允许您避免返回对象的性能损失。

我和很多不了解RVO的人一起工作,写过像:

void get_stuff(std::vector<int>& foo /*and input params*/) {
    // add a whole lot of integers into foo.
}

因为他们认为这比通过以下方式更便宜(避免副本):

void get_stuff(/*input params*/){
      std::vector foo;
      // populate foo.
      return foo;
}

这导致不必要的冗长,并且通常难以阅读代码。这是典型的过早优化 - 你不会犯的错误,因为你现在知道RVO!

答案 2 :(得分:1)

按值返回函数结果是其中一个地方,允许编译器将副本作为优化进行删除,从而将代码转换为道德等同于此:

void f(uint8_t* memory) {
    new(memory) C; // create object reserved memory location
    cout << (size_t)memory << endl; 
}

int main() {
    alignas(alignof(C)) uint8_t value[sizeof(C)]; //<- reserve properly aligned raw storage of appropriate size on the stack
    f(value);
    cout << (size_t)&value[0] << endl;
}

这种优化技术称为NRVO(命名返回值优化),实际上是大多数调用约定的一个非常自然的结果,它指定 - 对于无法通过寄存器返回的值 - 返回的值放在一个地址无论如何都是由调用者指定的。

答案 3 :(得分:0)

我不确定这是否真的是RVO。同样有效的假设是retvalue的存储都在实现的堆栈上,并且它们共享相同的堆栈槽,因为它们的生命周期实际上是非重叠的。可能有中间人。