如何发生“堆栈溢出”,你如何防止它?

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

在这种情况下,堆栈是在程序运行时放置数据的后进先出缓冲区。 最后一个出局(LIFO)意味着你最后放置的东西总是你第一件物品退出 – 如果你在堆叠上推送2个物品,'A'然后'B',那么你首先popupclosures堆栈将是'B',接下来是'A'。

在代码中调用函数时,函数调用之后的下一条指令将存储在堆栈中,并且可能会被函数调用覆盖的任何存储空间。 你调用的函数可能会为自己的局部variables使用更多的堆栈。 当它完成时,它释放它使用的局部variables堆栈空间,然后返回到前一个函数。

堆栈溢出

堆栈溢出是当你使用堆栈的内存超过你的程序应该使用的时候。 在embedded式系统中,堆栈可能只有256个字节,如果每个函数占用32个字节,则只能进行函数调用8个深层函数1个调用函数2调用函数3调用函数4 ….调用函数8调用函数9,但函数9覆盖堆栈外的内存。 这可能会覆盖内存,代码等

许多程序员通过调用函数A调用函数B,然后调用函数C,然后调用函数A,从而犯了这个错误。它可能大部分时间工作,但只是一旦错误的input将导致它永远在那个圈子里直到电脑识别到堆栈被夸大为止。

recursion函数也是一个原因,但是如果你正在recursion地编写(即你的函数自己调用),那么你需要知道这一点,并使用静态/全局variables来防止无限recursion。

一般来说,您使用的操作系统和编程语言都是用来pipe理堆栈的,而且它已经不在您的手中了。 你应该看看你的调用图(一个树状结构,显示每个函数调用的主要内容),以查看函数调用的深度,以及检测没有打算的循环和recursion。 故意的循环和recursion需要被人为地检查出来,如果他们互相调用了太多次。

除了良好的编程习惯,静态和dynamictesting之外,在这些高级系统上你可以做的不多。

embedded式系统

在embedded式领域,特别是在高可靠性代码(汽车,飞机,太空)中,您需要进行大量的代码审查和检查,但是您还需要执行以下操作:

  • 不允许recursion和循环 – 由策略和testing强制执行
  • 保持代码和堆栈相距甚远(代码在闪存中,堆栈在RAM中,而不是两者相遇)
  • 将保护带放在堆栈的周围 – 用魔法数字(通常是一个软件中断指令,但这里有很多选项)填满内存的空白区域,每秒钟观察数百或数千次防护带以确保他们没有被覆盖。
  • 使用内存保护(即不在堆栈上执行,不在堆栈外读写)
  • 中断不会调用二级函数 – 它们设置标志,复制数据,并让应用程序负责处理它(否则,你可能会深入到你的函数调用树中,产生一个中断,然后在中断,造成井喷)。 您有几个调用树 – 一个用于主进程,另一个用于每个中断。 如果你的中断可以打断对方…好吧,有龙…

高级语言和系统

但是在操作系统上运行的高级语言中:

  • 减less局部variables存储(局部variables存储在堆栈中 – 尽pipe编译器对此非常聪明,如果调用树较浅,有时候会把大型局部variables放在堆上)
  • 避免或严格限制recursion
  • 不要将程序过分分解为越来越小的函数 – 即使不计数局部variables,每个函数调用在堆栈上消耗多达64个字节(32位处理器,节省一半的CPU寄存器,标志等)
  • 保持你的调用树很浅(类似于上面的语句)

Web服务器

这取决于你所拥有的“沙箱”,你是否可以控制甚至看到堆栈。 机会是好的,你可以像对待任何其他高级语言和操作系统一样对待Web服务器 – 这在很大程度上不在您的手中,但请检查您正在使用的语言和服务器堆栈。 例如,可以在您的SQL服务器上放置堆栈。

-亚当

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

无限recursion是获取堆栈溢出错误的常用方法。 为了防止 – 总是确保有一个被击中的退出path。 🙂

另一种获得堆栈溢出(至less在C / C ++中)的方法是在栈上声明一个巨大的variables。

char hugeArray[100000000]; 

这将做到这一点。

大多数人会告诉你,在没有退出path的情况下发生堆栈溢出,而大多数情况下,如果使用足够大的数据结构,即使正确的recursion退出path也无济于事。

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

  • 广度优先search
  • 尾recursion ,.Net特定的伟大的博客文章 (对不起,32位.Net)

通常情况下,堆栈溢出是无限recursion调用的结果(现在标准计算机的内存量一般)。

当您拨打方法,function或程序时,“标准”方式或拨打电话的方式包括:

  1. 将呼叫的返回方向推入堆栈(这是呼叫之后的下一个句子)
  2. 通常返回值的空间被保留在堆栈中
  3. 将每个参数推入堆栈(顺序不同,取决于每个编译器,其中一些有时存储在CPU寄存器中以提高性能)
  4. 进行实际的通话。

所以,通常这取决于参数的数量和types以及机器体系结构。

你会看到,如果你开始recursion调用,堆栈将开始增长。 现在,堆栈通常被保留在内存中,使得堆栈的方向与堆的方向相反,因此,如果有大量的调用而没有“回来”,则堆栈开始变满。

现在,在较旧的时候,堆栈溢出可能会发生,因为你像所有的可用内存一样。 使用虚拟内存模型(在X86系统上高达4GB)通常不在范围之内,如果出现堆栈溢出错误,请寻找无限recursion调用。

当Jeff和Joel想给世界一个更好的地方来获得技术问题的答案时,会发生堆栈溢出。 防止堆栈溢出已经太迟了。 这个“其他网站”本来可以通过不被模糊来阻止它。 ;)

什么? 没有人会对无限循环中的那些人产生任何的爱?

            做
             {

                 JeffAtwood.WritesCode();

             (StackOverflow.MakingMadBank.Equals(false));

考虑到这被标记为“黑客”,我怀疑他指的“堆栈溢出”是一个调用堆栈溢出,而不是更高级别的堆栈溢出,如在这里大多数其他答案中引用的那些溢出。 它并不适用于.NET,Java,Python,Perl,PHP等任何托pipe或解释的环境,这些Web应用程序通常都是用这种方式编写的,所以唯一的风险就是Web服务器本身, C或C ++。

看看这个主题:

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

除了从直接recursion(例如Fibonacci(1000000) )得到的堆栈溢出的forms之外,我经常经历的更微妙的forms是间接recursion,其中一个函数调用另一个调用另一个函数的函数,然后其中一个函数再次调用第一个函数。

这通常发生在响应事件而被调用的函数中,但是它们本身可能产生新的事件,例如:

 void WindowSizeChanged(Size& newsize) { // override window size to constrain width newSize.width=200; ResizeWindow(newSize); } 

在这种情况下,对ResizeWindow的调用可能会导致再次触发WindowSizeChanged()callback,再次调用ResizeWindow ,直到您用完堆栈。 在这样的情况下,您经常需要推迟响应事件,直到堆栈帧返回,例如通过发布消息。