【问题标题】:Local varible (stack) returned as function return value作为函数返回值返回的局部变量(堆栈)
【发布时间】:2017-11-23 07:40:31
【问题描述】:

这是一个在互联网上找到的朋友功能示例:

#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle() {}
    Rectangle(const Rectangle &r) { 
        width = r.width; 
        height = r.height;
        cout << "copy\n";
    }
    Rectangle (int x, int y) : width(x), height(y) {}
    int area() {return width * height;}
    friend Rectangle duplicate (const Rectangle&);
};

Rectangle duplicate (const Rectangle& param)
{
  Rectangle res;
  res.width = param.width*2;
  res.height = param.height*2;
  return res;
}

int main () {
  Rectangle foo;
  Rectangle bar (2,3);
  foo = duplicate (bar);
  cout << foo.area() << '\n';
  return 0;
}

输出:

24

请注意,友元“duplicate”函数创建了一个局部变量并作为返回值返回给调用者。这不应该是一个局部变量并且分配在这个堆栈上吗?一旦“重复”完成执行,它不应该被销毁吗?这个例子好吗?

谢谢。

【问题讨论】:

  • 返回变量会复制它。
  • 从概念上讲,局部变量res是在栈上构造的,然后移入返回值,然后运行局部变量的析构函数。在实践中,编译器会优化这一点
  • 这不起作用的唯一情况是如果您尝试返回一个数组,因为当您返回数组时,数组会衰减为指针,并且指针指向一个被破坏的对象。
  • @DeanSeo 但它在概念上是一个副本,这是一种优化。
  • @Swift Rectangle 有一个自动生成的移动构造函数

标签: c++


【解决方案1】:

想想常规类型:

int getInt(void) {
    int a = 5:
    return a;
}

函数并没有真正返回局部变量a。相反,它返回a 的副本。同样,res 不是由函数返回,而是它的副本。

实际上,编译器很可能会检测到你的函数并通过避免复制Rectangle来优化函数。

要观察构造函数调用,编译它

g++ -fno-elide-constructors foo.cpp -o foo

您必须disable return value optimization for g++ (-fno-elide-constructors),这是一个非常基本的优化,即使使用-O0 也会启用。

【讨论】:

  • 不应该调用复制构造函数吗?我确实修改了代码添加了一个复制构造函数(见上面更新的代码),但是没有调用新的构造函数。
  • 如何禁用它?我希望能够验证并确保我了解这种巫术魔法。
  • 我刚做了。输出仍然没有变化。意味着没有调用复制构造函数。顺便说一句,我在 Windows 上使用 MinGW。后面我会用g++来验证。
  • @hebbo 最好放弃。我刚刚从g++ 了解到这是一个非常基本的优化。尝试寻找禁用它的方法。
  • @hebbo 我设法用g++ -fno-elide-constructors 获得了输出。
【解决方案2】:

您的函数按值返回 Rectangle,所以应该没问题。

Rectangle duplicate (const Rectangle& param)

返回的对象不在同一存储中,与从抽象语言级别的局部变量声明的对象不同。事实上,正如 Justin 所说,编译器可能会优化时间对象的创建。

如果你声明了,你会是红色的

Rectangle& duplicate (const Rectangle& param)

然后函数会尝试返回对局部变量的引用。

代码怎么写,相当于表达式。

return Rectangle(param.width*2, param.height*2);

复制构造函数:

Rectangle(const Rectangle &r) { 
    width = r.width; 
    height = r.height;
}

应该写成

Rectangle(const Rectangle &r): width(r.width), height(r.height) 
{ 
}

输出行是一个副作用,可能由于复制省略而被忽略。

【讨论】:

  • 不应该调用复制构造函数吗?我确实修改了代码添加了一个复制构造函数(见上面更新的代码),但是没有调用新的构造函数。
  • @hebbo RVO(返回值优化)和复制省略。正式的编译器可能会省略复制构造函数,这就是为什么你不应该依赖这样的体中的副作用。我恳请您浏览 ISO 草案并进行搜索。或搜索这些关键字以获取示例
  • 那么,这些返回值存储在哪里呢?他们如何在函数调用结束后幸存下来?什么决定了他们的范围?
  • @hebbo value 返回的是右值或等价物,所以它们的存储范围是调用发生的表达式。
猜你喜欢
  • 1970-01-01
  • 2013-09-27
  • 2014-09-06
  • 1970-01-01
  • 1970-01-01
  • 2015-08-11
  • 1970-01-01
  • 2020-09-11
  • 2018-06-14
相关资源
最近更新 更多