返回在堆中分配的实例与在堆栈中分配的实例

Returning instances allocated in the heap vs in the stack

本文关键字:实例 分配 堆栈 返回      更新时间:2023-10-16

距离我上次用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 createInStack() 
{
  MyClass return_value;
  return return_value;
}
int main() 
{
  MyClass inStack = createInStack();
}

那么是的,c在逻辑上是在createInStack()的堆栈上创建的,然后它的一个副本在逻辑上返回,然后在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,只是不创建它,但您可以非常肯定,至少命名返回值优化将避免大量复制。

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

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