2

有时,我看到存储持续时间和发生地点之间存在混合概念。那是因为有时我会看到以下声明:

int i; // This is in the stack!
int* j = new int; // This is in the heap!

但这真的 100% 正确吗?C++ 是否确保存储发生在哪里或者,它是由编译器决定的?

存储位置是否与持续时间无关?

例如,取这两个片段:

void something()
{
   int i;
   std::cout << "i is " << i << std::endl;
}

与:

void something()
{
   int* i = new int;
   std::cout << "i is " << i << std::endl;
   delete i;
}

两者都或多或少等效于 的生命周期i,它在块的开头创建并在块的末尾删除,这里编译器可以只使用堆栈(我不知道!),相反的情况也可能发生:

void something()
{
   int n[100000000]; // Man this is big
}

与:

void something()
{
  int* n = new int[100000000];
  delete n;
}

这两种情况应该在堆中以避免堆栈溢出(或者至少是到目前为止我被告知的......),除了存储持续时间之外,编译器是否也考虑到了这一点?

4

2 回答 2

3

存储位置是否与持续时间无关?

A0:持续时间指定预期/要求的行为。
A1:该标准没有具体说明如何实施。
A2:标准甚至不需要堆或栈!

void something()
{
   int i;
   std::cout << "i is " << i << std::endl;
}

void something()
{
   int* i = new int;
   std::cout << "i is " << i << std::endl;
   delete i;
}

在第一个示例中,您具有“自动”存储持续时间,而第二种情况是“动态”存储持续时间。不同之处在于“自动”总是在作用域结束时被销毁,而第二个只会在delete is executed.

标准没有指定创建对象的位置,完全留给实施。

在使用底层堆的实现上,这对于第一个示例来说是一个简单的实现选择;但不是要求。该实现可以很容易地调用操作系统来获取整数所需空间的动态内存,并且仍然像标准定义的那样运行,只要在对象超出范围时也植入和执行释放内存的代码。

相反,实现动态存储持续时间(第二个示例)的简单方法是从运行时分配内存,然后在您点击删除时释放它(假设您的实现具有此能力)。但这不是要求。如果编译器可以证明没有异常并且你总是会点击删除,那么它可以很容易地将它放在堆上并正常销毁它。注意:如果编译器确定对象总是泄漏。它仍然可以将它放在堆上,并且在它超出范围时根本不销毁它(这是一个完全有效的实现)。

第二组示例增加了一些复杂性:

代码:

int n[100000000]; // Man this is big

这确实非常大。某些实现可能无法在堆栈上支持这一点(堆栈帧大小可能受操作系统或硬件或编译器的限制)。

一个完全有效的实现是为此动态分配内存并确保在对象超出范围时释放内存。

另一种实现是简单地预先分配内存,而不是在堆栈上,而是在bzz(从这里的内存开始。这是存储内存的应用程序的汇编器区域)。只要它实现了在作用域末尾调用任何析构函数的预期行为(我知道 int 没有析构函数,所以它很容易)。

于 2019-12-29T13:26:42.820 回答
1

C++ 是否确保存储发生在哪里?还是由编译器决定?

当您声明如下变量时:

int i;

它具有自动存储功能。它确实可能在堆栈上,但如果有足够的寄存器可用,也通常只为它分配一个寄存器。理论上编译器为这个变量分配堆内存也是有效的,但实际上这不会发生。

当您使用new时,实际上是由标准库为您分配内存。默认情况下,它将使用堆。但是,理论上它也可以在堆栈上分配内存,但当然这通常是错误的做法,因为当您从调用的函数返回时,任何堆栈存储都会消失new

实际上,new它只是一个运算符,就像+,你可以重载它。通常,您会在类中重载它,但您也可以重载全局new运算符(类似地,delete运算符),并让它从您想要的任何地方分配存储。

存储位置是否与持续时间无关?

原则上是的,但在实践中,只有函数持续时间的自动变量被放置在堆栈上,而您分配的数据new通常旨在超过调用它的函数,并且在堆上进行。

这两种情况应该在堆中以避免堆栈溢出(或者至少是到目前为止我被告知的......),除了存储持续时间之外,编译器是否也考虑到了这一点?

据我所知,GCC 和 Clang 从不将堆分配用于具有自动存储的变量,无论它们的大小如何。因此,您必须自己使用newdelete自己,或者使用为您管理存储的容器。std::string如果您仅在其中存储少量元素,则某些容器(例如)将避免堆分配。

于 2019-12-29T13:27:26.310 回答