多少个嵌套函数调用?


15

MSDN引用有关StackOverflowException的信息

当执行堆栈溢出时引发的异常,因为它包含太多的嵌套方法调用。

Too many这里很模糊。我怎么知道什么时候真的太多了?数千个函数调用?百万?我认为它必须以某种方式与计算机中的内存量相关联,但是是否可以提出一个大致准确的数量级?

我对此很担心,因为我正在开发一个涉及大量使用递归结构和递归函数调用的项目。我不希望我的应用程序在开始用于多个小型测试时会失败。


4
将递归函数转换为非递归函数相对容易。只需将数据粘贴到Stack<T>
布莱恩(Brian)

1
“太多”到底有多大取决于您来定义。对于.NET,请使用editbin /stack:WHATEVER-NUMBER-YOU-LIKE yourexefile.exe
SK-logic

Answers:


28

我对此很担心,因为我正在开发一个涉及大量使用递归结构和递归函数调用的项目。我不希望我的应用程序在开始用于多个小型测试时会失败。

除非您的语言环境支持尾部调用优化(递归尾部调用),否则基本经验法则是:递归深度应确保为O(log n),即使用基于除法和除法的数据或数据结构征服(如树,大多数排序算法等)是可以的,但线性的任何东西(如链表处理的递归实现)都不行。


3
+1。很好的答案,我一直在隐式使用此规则,但我没有意识到。
Giorgio

在当今时代,几乎所有值得形容词短语的生产质量的编译器都将支持尾调用优化。
John R. Strohm

1
@ JohnR.Strohm然而,OP已为.NET标记了问题,因此AFAIK目前仅64位抖动会进行尾部调用优化。
马克·赫德

1
只是为了避免混淆,x86和x64都始终尊重CIL的“尾巴”。指令前缀。综上所述,在.NET领域中,F#会完成尾调用优化,而C#则不会,并且x64抖动会“简单”(无法依赖),因此会完成。见blogs.msdn.com/b/clrcodegeneration/archive/2009/05/11/...
斯蒂芬·斯文森

1
的确如此,但是在某些情况下,例如在臭名昭著的IIS中托管ASP.NET,即使是仅O(log n)递归深度也可能由于其可悲的,不合理的,可笑的微小堆栈深度限制而失败,这几乎导致了任何递归(或甚至一连串的非递归嵌套调用)都是不可能的。唯一的解决方法是对iis二进制文件本身进行editbin。
SK-logic

17

默认情况下,CLR为每个线程分配1 MB到堆栈(请参阅本文)。因此,要超出此数量,需要进行多次调用。具体取决于每个调用用于参数和局部变量之类的堆栈空间的多少。

StackOverflowException如果您有点不合常规,您甚至可以通过一次调用使其抛出:

private static unsafe void BlowUpTheStack()
{
    var x = stackalloc byte[1000000000];
    Console.WriteLine("Oh no, I've blown up the stack!");
}

不幸的是,这种合理的默认设置经常被违反(例如,在asp.net中)。
SK-logic

2

由于科尔·坎贝尔Cole Campbell)指出了内存大小,迈克尔·伯格沃德Michael Borgwardt)指出了尾部调用优化,因此我将不介绍这些内容。

要注意的另一件事是CPS,可用于优化多个交织的函数,其中尾部调用优化用于单个函数。

您可以像在所做的那样增加堆栈的大小,并注意64位代码比32位代码更快地吞噬了堆栈。

值得注意的是,我们在F#交互下运行了其中一个示例40多个小时,而没有感到震惊。是的,它是一个连续不断地运行到成功完成的函数调用。

另外,如果您需要进行代码覆盖以找出问题出在哪里,而您可以使用的VS没有代码覆盖,请使用TestDriven.NET

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.