如何发生"堆栈溢出"以及如何防止它?

时间:2020-03-05 18:42:51  来源:igfitidea点击:

堆栈溢出是如何发生的,以及确保堆栈溢出不会发生的最佳方法是什么,或者是防止堆栈溢出的最佳方法,特别是在Web服务器上,但是其他示例也很有趣?

解决方案

回答

实际代码中的堆栈溢出很少发生。发生终止的大多数情况是被忘记终止的递归。然而,它可能很少发生在高度嵌套的结构中,例如特别大的XML文档。唯一真正的帮助是将代码重构为使用显式堆栈对象而不是调用堆栈。

回答

无限递归是获取堆栈溢出错误的常用方法。为避免发生这种情况,请务必确保有一条出口会被撞到。 :-)

导致堆栈溢出(至少在C / C ++中)的另一种方法是在堆栈上声明一些巨大的变量。

char hugeArray[100000000];

那样做。

回答

大多数人会告诉我们,在没有退出路径的情况下进行递归会发生堆栈溢出,而大多数情况下是这样,如果我们使用足够大的数据结构,那么即使递归退出路径也无济于事。

在这种情况下的一些选项:

  • 广度优先搜索
  • 尾递归,.Net特定的出色博客文章(对不起,32位.Net)

回答

通常,堆栈溢出是无限递归调用的结果(鉴于当今标准计算机中的常用内存量)。

当我们以"标准"方式调用方法,函数或者过程或者进行调用时,取决于:

  • 将调用的返回方向推入堆栈(即调用之后的下一个句子)
  • 通常,返回值的空间保留在堆栈中
  • 将每个参数压入堆栈(顺序各不相同,并取决于每个编译器,有时也将其中一些存储在CPU寄存器中以提高性能)
  • 进行实际通话。

因此,通常这要花一些字节,具体取决于参数的数量和类型以及机器的体系结构。

然后我们会看到,如果我们开始进行递归调用,则堆栈将开始增长。现在,堆栈通常以一种与堆相反的方向增长的方式保留在内存中,因此,如果有大量调用而没有"返回",则堆栈开始变满。

现在,在较早的时候,堆栈溢出可能仅是因为我们耗尽了所有可用的内存,就像这样。由于虚拟内存模型(在X86系统上最大为4GB)超出了范围,因此通常会出现堆栈溢出错误,请寻找无限递归调用。

回答

当Jeff和Joel希望给世界一个更好的地方来获取技术问题的答案时,就会发生堆栈溢出。为防止该堆栈溢出为时已晚。该"其他站点"本可以通过避免混淆来阻止它。 ;)

回答

考虑到这被标记为" hacking",我怀疑他指的"堆栈溢出"是调用堆栈溢出,而不是更高级别的堆栈溢出(如此处大多数其他答案中所引用的那样)。它实际上并不适用于通常编写Web应用程序的任何托管或者解释环境(例如.NET,Java,Python,Perl,PHP等),因此,唯一的风险就是Web服务器本身,可能是使用Web Server编写的。 C或者C ++。

看看这个线程:

https://stackoverflow.com/questions/7308/what-is-a-good-starting-point-for-learning-buffer-overflow

回答

在这种情况下,堆栈是我们在程序运行时放置数据的后进先出缓冲区。后进先出(LIFO)表示,如果我们先按栈中的2个项目(" A"然后是" B"),最后放入的东西始终是取出的第一件事,然后弹出的第一件事堆栈将是" B",下一个是" A"。

当我们在代码中调用函数时,函数调用后的下一条指令将存储在堆栈中,并且所有可能被函数调用覆盖的存储空间都将存储在堆栈中。我们调用的函数可能会为自己的局部变量消耗更多的堆栈。完成后,它将释放它使用的局部变量堆栈空间,然后返回到上一个函数。

堆栈溢出

堆栈溢出是指我们已为堆栈使用了比程序本应使用的内存更多的内存。在嵌入式系统中,堆栈可能只有256个字节,如果每个函数占用32个字节,则只能进行函数调用8深层函数1调用函数2谁调用函数3谁调用函数4 ....谁调用函数8调用函数9,但是函数9覆盖堆栈外部的内存。这可能会覆盖内存,代码等。

许多程序员通过调用函数A然后调用函数B,然后调用函数C,然后调用函数A来犯此错误。它在大多数情况下都可以工作,但是一旦输入错误,它就会永远进入该循环。直到计算机识别出堆栈已过满。

递归函数也是造成这种情况的原因,但是如果我们递归地编写代码(即函数调用自身),则需要意识到这一点并使用静态/全局变量来防止无限递归。

通常,我们使用的操作系统和编程语言可以管理堆栈,而这并非我们所能控制。我们应该查看自己的调用图(从主视图显示每个函数调用内容的树状结构),以了解函数调用进行的深入程度,并检测非预期的循环和递归。如果有意的循环和递归相互调用太多次,则需要人为地检查它们是否出错。

除了良好的编程习惯,静态和动态测试之外,在这些高级系统上我们无能为力。

嵌入式系统

在嵌入式世界中,尤其是在高可靠性代码(汽车,飞机,太空)中,我们需要进行大量的代码检查和检查,但是我们还需要执行以下操作:

  • 禁止递归和周期-由策略和测试强制执行
  • 将代码和堆栈分开(闪存中的代码,RAM中的代码,并且永远不会相遇)
  • 将保护带放置在堆栈周围-用魔术数字填充的内存空白区域(通常是软件中断指令,但是这里有很多选项),每秒查看保护带以确保发现数百或者数千次他们没有被覆盖。
  • 使用内存保护(即在堆栈上不执行,在堆栈外不进行读取或者写入)
  • 中断不会调用辅助函数-它们会设置标志,复制数据,并让应用程序负责处理它(否则,我们可能在函数调用树中获得了8个深度,产生了一个中断,然后在内部调用了另外几个函数中断,导致井喷)。我们有几个调用树-一个用于主进程,一个用于每个中断。如果你们的干扰能互相干扰...好吧,那有龙...

高级语言和系统

但在高级语言中,这些语言可以在操作系统上运行:

  • 减少局部变量存储(局部变量存储在堆栈上-尽管编译器对此非常聪明,并且如果调用树较浅,有时会将大局部变量放在堆上)
  • 避免或者严格限制递归
  • 不要将程序分解成越来越小的函数-即使不计算局部变量,每个函数调用在堆栈上也要占用多达64个字节(32位处理器,节省了一半的CPU寄存器,标志等)
  • 保持调用树浅(类似于上面的语句)

Web服务器

是否可以控制甚至查看堆栈取决于我们拥有的"沙盒"。我们很有可能可以像对待其他高级语言和操作系统一样随意使用Web服务器,这在很大程度上是我们无法控制的,但是请检查所使用的语言和服务器堆栈。例如,可以在SQL Server上释放堆栈。

-亚当

回答

什么?没有人会对无限循环造成的伤害有爱吗?

do
{
  JeffAtwood.WritesCode();
} while(StackOverflow.MakingMadBank.Equals(false));