【问题标题】:Returning instances allocated in the heap vs in the stack返回在堆中分配的实例与在堆栈中分配的实例
【发布时间】:2013-04-22 08:20:05
【问题描述】:

距离我上一次用 C++ 做某事已经很多很多年了。这些天来,有人向我寻求 C++ 学校项目的帮助,我对我所见过的语言的一个“特性”很感兴趣,它运行良好,但我认为它不起作用。

我记得,我可以在堆上或堆栈上创建类的实例:

int main() {
  MyClass *inHeap = new MyClass();
  MyClass inStack = MyClass();
}

据我所知,第一个变量inHeap,将使编译器在main 堆栈帧中保留一些堆栈,足以容纳一个指针(4 个字节?8 个字节? 类似的东西),它指向堆中对象的实际实例。

另外,第二个变量inStack,将使编译器保留足够的堆栈以在main 堆栈帧中保存MyClass 的完整实例。

现在,我的问题。假设我有一个应该返回MyClass 实例的函数。一开始,我以为它只能返回堆中的实例:

MyClass *createInHeap() {
  return new MyClass();
}
int main() {
  MyClass* inHeap = createInHeap();
}

但我看到的是以下内容:

MyClass createInStack() {
  MyClass c = MyClass();
  return c;
}
int main() {
  MyClass inStack = createInStack();
}

这里到底发生了什么?

  • MyClass 实例的内存是否保留在createInStack 的堆栈帧中?如果是这种情况,当函数createInStack 返回时,这段代码是否会强制实例复制main 的堆栈帧中?这个拷贝是如何执行的,即它只是在main函数中为我自动调用了拷贝构造函数吗?

  • 我想到的另一种可能性是编译器足够聪明,已经为main 堆栈帧中的MyClass 实例保留了内存。这存在吗?这是某种优化以避免制作可能昂贵的副本吗?

作为最后一个问题,当我在堆栈中创建了一个实例时,它的析构函数究竟是什么时候调用的?创建它的作用域何时结束?

【问题讨论】:

  • 除非我遗漏了什么,否则您写的所有内容的答案都是“是”:)
  • 注意,MyClass c(); 确实创建类 MyClass 的实例,而是声明了一个不带参数并返回类型为 MyClass 的对象的函数. MyClass inStack(); 也一样。
  • MyClass inStack(); 是一个函数声明;你的意思是MyClass inStack;。除此之外,答案几乎是“是”。
  • 谢谢,伙计们。我修复了该函数调用与实例创建。

标签: c++ heap-memory allocation stack-memory


【解决方案1】:

如果你正在做这样的事情:

MyClass createInStack() 
{
  MyClass return_value;
  return return_value;
}

int main() 
{
  MyClass inStack = createInStack();
}

那么是的,ccreateInStack() 中的堆栈上逻辑创建,然后逻辑上返回它的副本,然后将其复制到main 中的inStack 中。

由于所有这些逻辑复制,有一个常见的误解是按值返回效率低下。但是,由于命名返回值优化,这并不是实际发生的情况。调用函数中的构造步骤只是推迟到被调用函数。应该是这样的(伪代码):

void createInStack(MyClass &return_value)
{
  return_value.MyClass(); // construct return_value (not actually valid syntax)
}

int main()
{
  MyClass inStack; // except don't call the constructor
  createInStack(inStack);
}

如您所见,没有实际的复制发生。

此外,编译器可能会进行其他优化。它甚至可能决定从不使用 inStack 并且只是不创建它,但您可以确定至少命名的返回值优化将避免大量复制。

【讨论】:

    【解决方案2】:

    还应该指出,调用new 并不是一项无成本的操作。它可能有很大的开销,而且它肯定比在堆栈上制作本地副本使用更多的内存 - 当然,delete 必须由程序员处理和记住,而且花费的时间也超过零。

    因此,像往常一样,这是一个更好的设计决策,并且了解正在发生的事情(以及了解先前答案中已经涵盖的“返回值优化”)。一个非常大的类需要很长时间来复制,但是一个只有几个值的小类,在堆栈上复制可能比分配和释放它更快。

    【讨论】:

      猜你喜欢
      • 2012-10-04
      • 2015-01-20
      • 2011-10-06
      • 1970-01-01
      • 2012-01-23
      • 1970-01-01
      • 2016-09-02
      • 2021-04-01
      • 1970-01-01
      相关资源
      最近更新 更多