C++标准对栈溢出有何规定?

11
我查看了草案C++0x标准,据我所知其中没有任何关于栈溢出的内容。搜索“stack overflow”没有结果,搜索“stack”只有对栈展开和std::stack的引用。那是否意味着遇到本地对象(例如巨大的本地数组)耗尽内存时不允许处理错误的机制,因此无法实现符合C++标准的实现?
回答这个问题的答案表明至少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在栈上分配了1艾字节的内存(想象一下如果函数实际使用了巨大的数组,编译器就无法将其静默地删除而不产生任何问题),因此该程序在任何现代计算机上都无法运行。C++标准是否允许程序A失败?
编辑:问题并不是关于标准应该如何定义栈溢出时会发生什么,而是关于标准是否有规定,无论它是否存在。

2
标准没有关于具有有限内存的物理计算机的概念。原则上,没有编译器可以符合标准,因为您始终可以编写一个非常大的程序,它无法适应任何现有的硬件并且无法编译,尽管它由有效的标准代码组成,但编译器无法编译它。 - Kerrek SB
3
@Kerrek 很有道理。然而,标准确实把内存作为有限资源来考虑,如果使用 new 分配了大型数组,就会抛出 std::bad_alloc 异常。我想知道的是,是否有类似的概念允许程序 A 失败。 - Bjarke H. Roune
你当然可以期望 new char[HUGE] 失败,没错。但是如果你的源代码包含了数十亿个函数呢?而且这些函数都是完全有效的标准 C++ 代码... - Kerrek SB
2
栈是实现细节,C和C++标准都不会碰它。这并不罕见,VM语言也会混淆它。这普遍适用于“哦,该死,我们不要再做那个了”的类别。如果你的栈用完了,那么你已经超出了合理范围一个数量级。 - Hans Passant
@Hans: 如果你的栈空间不足,那么你就达到了一个不合理的数量级。这取决于具体的实现方式,某些实现方式(特别是如果它们没有虚拟内存)默认情况下不会为进程/线程提供比合理值高一个数量级的栈空间。在 PC 上,现代操作系统当然会提供这个功能。 - Steve Jessop
显示剩余13条评论
4个回答

14
我不确定这是否是您要查找的,但在C++03 ISO标准的附录B中有以下通知:
  1. 由于计算机是有限的,C++实现在其可以成功处理的程序大小方面不可避免地受到限制。每个实现都应当记录已知的那些限制。此文档可能会引用固定的限制(如果存在),说明如何将变量限制作为可用资源的函数进行计算,或者说固定的限制不存在或未知。
  2. 这些限制可能会限制描述如下或其他数量的数量。
(我强调)我的理解是,只要编译器声明了有哪些限制以及它们如何根据系统可用资源进行计算,就可以使其中一个函数运行成功而使另一个函数失败,这是完全合法的。

如果标准只是这方面的内容,那么是的,那正是我要找的。谢谢。 - Bjarke H. Roune
2
顺便提一下,标准没有“堆栈溢出”的概念,这使得编译器可以执行积极的尾递归优化。(例如,请参见https://dev59.com/ym035IYBdhLWcg3wT-RC) - Matteo Italia
如果你继续阅读,那个部分就没有关于堆栈的内容了。这有点便宜,因为标准没有提到堆栈。然而,附录B对递归调用的数量或自动变量可以分配的内存量没有任何限制。这是件好事,因为这样的限制超出了编译器的控制范围。它们由操作系统和恶意系统管理员实施,他们将堆栈限制设置得太低。 - David Hammen
1
标准没有提到堆栈,因为堆栈和虚函数表一样是标准的一部分(或者说非常少)。标准定义了编译器必须以某种方式实现的“特性”,例如变量在超出作用域时被清除,函数返回后可能会递归到它们被调用的位置。碰巧堆栈非常适合这些事情(就像虚函数表碰巧非常适合虚继承一样),因此所有已知的编译器都使用堆栈,但这只是巧合和一个(不重要的)实现细节。 - Damon

4

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

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

没有任何规定说明如果一个程序不违反IS中的规则,但不能在实现的资源限制内被接受和正确执行,将会发生什么。因此对于这种情况,行为是未定义的。


1

堆栈溢出会破坏操作系统的保护机制。这不是语言的特性,因为所有的机器可执行代码都有相同的保护机制。

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

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


1
除了作为操作系统的一个特性外,它还会影响C++程序的可观察行为。因此,它既与C++有关,也与操作系统有关。例如,我在Java中遇到了堆栈溢出异常,因此其他一些语言确实定义了堆栈溢出。 - Bjarke H. Roune
@Bjarke:我认为这不会影响程序的可观察行为。我的意思是,显然,如果程序意外终止,你可以观察到发生了这种情况,但你肯定无法观察到其余的输出。但我们并不说Windows任务管理器使一个实现不符合规范,因为你可以终止正在运行的进程,而且我认为堆栈监视器的任意操作与用户的任意操作在标准的角度来看并没有太大区别。当和为什么操作系统终止一个进程超出了标准的范围。 - Steve Jessop

1

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接