如何发生堆栈溢出以及确保它不会发生的最佳方法是什么,或者是哪种方法可以防止,特别是在Web服务器上,但其他示例也会很有趣?
在此上下文中,堆栈是在程序运行时放置数据的最后进先出缓冲区.最后一次出来(LIFO)意味着你输入的最后一件事总是你要退回的第一件事 - 如果你在堆叠上推2个项目,'A'然后'B',那么你首先要弹出的东西在堆栈中将是'B',接下来是'A'.
当您在代码中调用函数时,函数调用之后的下一条指令将存储在堆栈中,以及可能被函数调用覆盖的任何存储空间.您调用的函数可能会为其自己的局部变量使用更多堆栈.完成后,它释放它使用的局部变量堆栈空间,然后返回到上一个函数.
堆栈溢出是指您为堆栈耗尽的内存超出了程序应该使用的内存.在嵌入式系统中,堆栈可能只有256个字节,如果每个函数占用32个字节,那么你只能有函数调用8深 - 函数1调用函数2调用函数3调用函数4 ....函数8调用函数9,但函数9覆盖堆栈外的内存.这可能会覆盖内存,代码等.
许多程序员通过调用函数A然后调用函数B来犯这个错误,然后调用函数C然后调用函数A.它可能在大多数情况下工作,但只是一次错误的输入将导致它永远进入该循环直到计算机识别出堆栈被夸大了.
递归函数也是导致这种情况的原因,但是如果你正在递归编写(即,你的函数自己调用),那么你需要注意这一点并使用静态/全局变量来防止无限递归.
通常,您正在使用的操作系统和编程语言管理堆栈,而且它不在您的手中.您应该查看您的调用图(从您的主要显示每个函数调用的树结构),以查看函数调用的深度,以及检测非预期的循环和递归.如果他们互相称呼过多次,则需要人为地检查故意循环和递归以防错误.
除了良好的编程实践,静态和动态测试之外,您无法在这些高级系统上做太多事情.
在嵌入式领域,特别是在高可靠性代码(汽车,飞机,太空)中,您可以进行大量的代码审查和检查,但您还要执行以下操作:
禁止递归和循环 - 由策略和测试强制执行
保持代码和堆栈相隔很远(代码在flash中,堆栈在RAM中,而且twain不会满足)
将保护带放置在堆栈周围 - 空白的内存区域,您填充了一个幻数(通常是软件中断指令,但这里有很多选项),每秒数百或数千次看保护带以确保他们没有被覆盖.
使用内存保护(即,堆栈上没有执行,堆栈外没有读取或写入)
中断不会调用辅助函数 - 它们设置标志,复制数据,并让应用程序负责处理它(否则你可能会在函数调用树中深入8,有一个中断,然后在内部运行另外几个函数)中断,引起井喷).你有几个调用树 - 一个用于主进程,一个用于每个中断.如果你的中断可以互相打断......好吧,有龙......
但是在操作系统上运行的高级语言:
减少局部变量存储(局部变量存储在堆栈中 - 尽管编译器非常聪明,如果调用树很浅,有时会将大型本地变量放在堆上)
避免或严格限制递归
不要将程序分解成越来越小的函数 - 即使不计算局部变量,每个函数调用在堆栈上消耗多达64个字节(32位处理器,节省一半CPU寄存器,标志等)
保持呼叫树浅(类似于上面的陈述)
它取决于您拥有的"沙盒",无论您是否可以控制甚至看到堆栈.很有可能你可以像处理任何其他高级语言和操作系统一样对待Web服务器 - 它很大程度上取决于你的手,但检查你正在使用的语言和服务器堆栈.这是可能的吹堆栈SQL服务器上,比如.
-亚当
实际代码中的堆栈溢出很少发生.它发生的大多数情况是递归,其中忘记了终止.然而,它可能很少出现在高度嵌套的结构中,例如特别大的XML文档.这里唯一真正的帮助是重构代码以使用显式堆栈对象而不是调用堆栈.
大多数人会告诉你,没有退出路径的递归会发生堆栈溢出 - 而大多数情况下,如果使用足够大的数据结构,即使正确的递归退出路径也无法帮助你.
在这种情况下的一些选项:
广度优先搜索
尾递归,特定于.Net的博客文章(对不起,32位.Net)
无限递归是获得堆栈溢出错误的常见方法.要防止 - 始终确保有一个将被击中的退出路径.:-)
另一种获得堆栈溢出的方法(至少在C/C++中)是在堆栈上声明一些巨大的变量.
char hugeArray[100000000];
那就行了.
当Jeff和Joel想要给世界一个更好的地方来获得技术问题的答案时,会发生堆栈溢出.防止这种堆栈溢出为时已晚.那个"其他网站"本可以通过不被嘲弄来阻止它.;)