返回C++栈变量

9
在这个例子中,为什么返回一个堆栈变量是可以的?当t()函数返回时,为什么不会返回垃圾值,因为堆栈指针已经被增加了呢?
#include << string >>
#include << vector >>
#include << iostream >>

using namespace std;

class X{
public:

  X() { cout << "constructor" << endl; }

  ~X() { cout << "destructor" << endl; }

};

vector <X> t()
{
  cout << "t() start" << endl;

  vector<X> my_x;

  int i = 0;

  printf("t: %x %x %x\n", t, &my_x, &i);

  my\_x.push\_back(X()); my\_x.push\_back(X()); my\_x.push\_back(X());

  cout << "t() done" << endl;

  return my_x;
}

int main()
{

  cout << "main start" << endl;

  vector <X> g = t();


  printf("main: %x\n", &g);

  return 0;

}

输出:

./a.out
main start
t() start
t: 8048984 bfeb66d0 bfeb667c
constructor
destructor
constructor
destructor
destructor
constructor
destructor
destructor
destructor
t() done
main: bfeb66d0
destructor
destructor
destructor

2
将一个指向堆栈变量的引用返回给调用者是危险的,因为可能会得到垃圾值。否则,将会构造一个副本并交给调用者。 - Mehrdad Afshari
4个回答

25

基本上,当您返回栈变量my_x时,您会调用复制构造函数来创建该变量的新副本。 然而,在这种情况下并不是如此,感谢全能的编译器。

编译器通过使用称为按值返回优化的技巧,使得变量my_x实际上是在分配给main方法中的g的内存位置处构建的。这就是为什么您看到打印了相同的地址bfeb66d0。这避免了内存分配和复制构造。

有时,由于代码的复杂性,这根本不可能,然后编译器重置为默认行为,创建对象的副本。


5
因为参数是按值传递的,所以会创建一个副本。因此,返回的不是堆栈上的值,而是它的副本。

实际上,通过命名的RVO并没有创建副本,就像smink所说的,并且追踪证明了这一点(函数t中my_x的地址与主函数main中g的地址相等)。但是,结果与创建副本的情况下有所不同。 - Steve Jessop
即使没有进行优化,他的代码也是有效的,因为该值被复制,所以返回的不是对函数中声明的不再有效的对象的引用。 - jalf

0

当一个副本被返回时,唯一无法返回副本的构造是静态数组。因此,您不能说这个...

int[] retArray()
{
    int arr[101];

    return arr;
}

0

编译器经过优化,可以处理返回“堆栈变量”而不调用复制构造函数的情况。您需要知道的唯一事情是,内存分配在分配它的函数和返回对象的函数的范围内。

它不会调用复制构造函数,也不会分配两次。

可能会有一些特殊情况,当然这取决于编译器--例如,原始类型可能会被复制--但通常,在返回时分配在堆栈上的对象不会被复制。

示例:

struct Test {

};

Test getTest() {
    Test t;
    std::cout << &t << std::endl;        // 0xbfeea75f
    return t;
}


int main(int argc, char *argv[])
{
    Test t = getTest();
    std::cout << &t << std::endl;       // also 0xbfeea75f
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接