【问题标题】:What does the C++ standard say about stack overflow?C++ 标准对堆栈溢出有什么看法?
【发布时间】:2011-07-05 23:20:20
【问题描述】:

我查看了 C++0x 标准草案,据我所知,其中没有任何关于堆栈溢出的内容。搜索“堆栈溢出”没有结果,搜索“堆栈”我只得到了对堆栈展开和 std::stack 的引用。这是否意味着不存在符合 C++ 标准的实现,因为当本地对象(例如巨大的本地数组)耗尽内存时,没有允许处理错误的机制?

this question 的答案表明至少 C 标准没有提到堆栈溢出。

为了使问题具体化,请考虑这个程序

// Program A
int identity(int a) {
  if (a == 0)
    return 0;
  char hugeArray[1024 * 1024 * 1024]; // 1 GB
  return identity(a - 1) + 1;
}
int main() {
  return f(1024 * 1024 * 1024);
}

还有这个程序

// program B
int main() {
  return 1024 * 1024 * 1024;
}

我认为 C++ 标准不允许任何 C++ 实现在这两个程序上做明显不同的事情。实际上,程序 A 不会在任何现代机器上运行,因为它在堆栈上分配了 EB 的内存(想象一下该函数实际上使用了巨大的数组,因此编译器无法静默删除它而不会产生不良影响)。 C++ 标准是否允许程序 A 失败?

编辑:问题不是标准是否应该定义堆栈溢出时会发生什么,问题是什么,如果有的话。

【问题讨论】:

  • 该标准没有内存有限的物理计算机的概念。原则上,任何编译器都不可能符合标准,因为您总是可以编写一个 非常 大的程序,该程序不适合任何现有硬件并且无法编译,尽管它由有效的标准代码组成,但编译器无法编译。
  • @Kerrek 好点。但是,就新的抛出 std::bad_alloc 而言,该标准确实具有内存是有限资源的概念。如果我使用 new 分配了巨大的数组,标准 允许程序 A 失败。我想知道是否有类似的概念允许程序 A 失败。
  • 你当然可以期待new char[HUGE] 失败,当然。但是,如果您的源代码包含数十亿个函数怎么办?所有完全有效的标准 C++...
  • 堆在某种程度上是显式分配的;堆栈是自动分配的,唯一可用的控制通常是堆栈探测器以强制扩展堆栈。这意味着您无法事先检查并且通常无法处理甚至识别堆栈运行到堆中的情况。 (想一想:这是一个超大的堆栈分配还是一个野堆指针?程序员可以通过检查代码(希望如此)来解决这个问题,但内核和系统库都不能。)
  • 堆栈是一个实现细节,无论是 C 还是 C++ 标准都不会触及十英尺的柱子。这并不少见,VM 语言也会混淆它。这普遍符合“哦,废话,我们不要再这样做了”的类别。如果你的堆栈用完了,那么你的数量级就超出了合理范围。

标签: c++ stack-overflow undefined-behavior


【解决方案1】:

我不确定这是否是您要查找的内容,但在 C++03 ISO 标准的附录 B 中有以下通知:

  1. 由于计算机是有限的,C++ 实现不可避免地受限于它们所使用的程序的大小 可以成功处理。每个实施都应在已知的情况下记录这些限制。本文档可能会引用存在的固定限制,说明如何计算可变限制作为 可用资源,或者说固定限制不存在或未知。
  2. 这些限制可能会限制数量,包括以下描述的数量或其他数量。

(我的重点)我认为这意味着编译器允许其中一个函数在另一个函数失败时工作是完全合法的,只要编译器说明存在哪些限制以及如何从资源中计算它们系统可用。

【讨论】:

  • 如果这就是关于此事的所有标准,那么是的,这正是我正在寻找的。谢谢。
  • 顺便说一句,标准没有“堆栈溢出”概念的事实允许编译器执行积极的尾调用优化。 (参见例如stackoverflow.com/questions/5493688/…
  • 如果您进一步阅读,堆栈上的该部分中没有任何内容。这有点便宜,因为标准没有提到堆栈。但是,附件 B 对递归调用的数量或自动变量可以分配的内存量没有限制。好事,因为这样的限制超出了编译器的控制。它们是由操作系统和将堆栈限制设置得太低的邪恶系统管理员安置的。
  • 该标准没有提到堆栈,因为堆栈与 vtables 一样是标准的一部分(或者...很少)。该标准定义了编译器必须以某种方式实现的特性,例如变量超出范围时被清理,函数可能在递归之后返回到它们被调用的位置。 偶然碰巧堆栈非常适合这些事情(例如 vtables 偶然非常适合虚拟继承),因此所有已知的编译器都使用堆栈,但这只是巧合,以及一个(不重要的)实现细节。
【解决方案2】:

行为未定义,因为标准没有定义超出资源限制的程序会发生什么。请注意,规范的附录 B 中有推荐的限制。该附件是非规范性的,实施可以忽略该附件,包括具有与此处指定不同的限制。在 1.4 [intro.compliance] 中,规范说

如果程序不违反本国际标准中的规则,则符合要求的实现应在其资源限制内接受并正确执行该程序。

对于不违反 IS 规则但在实施的资源限制内不能被接受和正确执行的程序,没有任何规定会发生什么。因此,这种情况下的行为是不确定的。

【讨论】:

    【解决方案3】:

    堆栈溢出破坏了操作系统已有的保护机制。它不是该语言的一个特性,因为所有机器可执行代码都将具有相同的保护。

    如果您想捕捉这个特定的错误,您需要编写特定于操作系统的代码。例如,在 Linux 上,您需要捕获 SIGSEGV(分段错误)信号。但是,请注意,这也可能由 NULL 指针引用或任何其他内存保护问题引起,而不仅仅是堆栈溢出。

    不确定 Windows、OSX 或移动设备。

    【讨论】:

    • 除了作为操作系统功能之外,它还影响 C++ 程序的可观察行为。所以它既与 C++ 有关,也与操作系统有关。例如,我在 Java 中遇到了堆栈溢出异常,因此其他一些语言确实定义了堆栈溢出。
    • @Bjarke:我认为它不会影响程序的可观察行为。我的意思是,很明显,如果程序意外终止,那么您可以观察到已经发生的情况,您肯定不会观察到其余的输出。但是我们并不是说 Windows 任务管理器会因为您可以杀死正在运行的进程而使实现不符合标准,而且我认为堆栈监视器的任意操作与用户的任意操作并不完全不同,就标准而言。操作系统终止进程的时间和原因超出了标准的范围。
    【解决方案4】:

    堆栈溢出时发生的情况非常依赖于系统(CPU 和操作系统,有时还有编译器,因为要由编译器插入堆栈探测器和其他安全扩展堆栈的机制),因此不可能强制执行特定响应;可以做的最好的事情是建议最好的响应如果目标平台允许它。大多数没有;虽然有一种合理的方法来处理堆溢出,但堆栈溢出处理程序 (a) 可能会在堆栈处于不一致状态时被调用,并且上面有部分构造的堆栈帧,并且 (b) 可能涉及调用一个处理程序......它需要用于中断帧的堆栈空间。 POSIX 指定了sigaltstack() 机制,但它也有限制,而且 ANSI C C/C++ 不能合理地依赖于 POSIX 合规性。

    【讨论】:

      猜你喜欢
      • 2012-05-10
      • 2010-11-09
      • 2012-12-20
      • 2011-03-02
      • 2014-01-17
      • 1970-01-01
      • 1970-01-01
      • 2014-12-20
      相关资源
      最近更新 更多