【问题标题】:stackoverflow by a recursion [duplicate]通过递归进行stackoverflow [重复]
【发布时间】:2012-07-03 22:12:27
【问题描述】:
可能重复:
Runtime exception, recursion too deep
我在开发 ac#.net 程序时遇到了一个问题,我已将其简化为一个简单的问题,我需要了解如果我这样调用函数,为什么这段代码会引发 stackoverflow 异常:
CheckFunc(16000);
但是如果我这样称呼它就可以了
CheckFunc(1000);
这里是函数:
private void CheckFunc(Int32 i)
{
if (i == 0)
MessageBox.Show("good");
else
CheckFunc(i - 1);
}
试图使代码尽可能简单......
我知道有一个堆栈溢出但哪个堆栈?我该如何解决这个问题?
谢谢。
【问题讨论】:
标签:
c#
exception
recursion
stack-overflow
【解决方案1】:
那基本上是因为你对递归的使用是错误的。
溢出的栈是进程的调用栈。堆栈用于发送给方法的参数、调用的返回地址以及方法中的局部变量。
对于每次调用,都会将其添加到堆栈中:
+---------------------+
| |
...
| |
+---------------------+ <-- stack pointer before call
| parameter: int |
+---------------------+
| return address |
+---------------------+
| stack frame for |
| local variables |
+---------------------+ <-- stack pointer in the call
每次递归调用都会向堆栈添加另一个块,并且由于堆栈空间有限(2 MB IIRC),如果您执行的递归深度达到数千级,您将填满堆栈。
当以一种好的方式使用递归时,您宁愿尝试将要处理的数据分成两半,而不是削减单个数据。基本上:
private void CheckFunc(int i) {
if (i == 0) {
MessageBox.Show("good");
} else {
CheckFunc(i / 2);
}
}
对于任何整数值,此递归永远不会超过 31 级。
【解决方案2】:
调用堆栈溢出。
每当你调用一个函数(或任何编程语言称之为“子例程”、“方法”等)时,CPU 都会存储函数完成后它必须返回的位置的地址。此外,“局部”变量或参数通常也存储在堆栈中。
那个栈有一个固定的大小,通常可以通过一些魔法来增加——当你创建一个线程时,你通常可以提供一个栈大小,当你链接一个程序时,可能还有一个链接器选项来设置一个栈大小.
基本上,您的代码会在堆栈上创建 16000 个“i”副本,以及 16000 个返回指针,以及编译器在函数调用时可能存储在堆栈中的任何内容。在你的另一次尝试中,你只复制了 1000 份这些东西。
当然,你正在做一些叫做“尾递归”的东西,它应该被优化掉;不要问我为什么你的编译器不这样做。
【解决方案3】:
您需要了解堆栈,以及它是如何在程序执行中使用的。简而言之,您的函数失败是因为它递归调用自身太多次。栈和堆一样,具有有限的大小,但与堆不同的是,它通常要小得多。每次您的函数调用自身时,堆栈中的一些内存用于保存有关函数调用和函数本地变量的信息(在您的示例中对变量 i 的引用)。这称为堆栈帧。当由于递归太深而创建的堆栈帧过多时,就会出现堆栈溢出。
您应该删除 CheckFunc 中的递归,并改为从循环中调用它。
【解决方案4】:
C# 中有 Call Stack 的限制。在第一种情况下,您超过了这个数字,所以您会得到 Stack Overflow Exception。
没有办法解决这个问题,但你自然可以通过减少递归深度来逃避它。
【解决方案5】:
问题确实是堆栈溢出。
为什么会发生:
堆栈是一个特殊的内存区域,其中存储了一些东西:
- 函数的局部变量
- 函数参数
- (最重要的是)函数返回地址。当您从函数返回时,处理器就是这样知道从哪里返回的。
问题是这个内存区域是有限的。递归调用将在此堆栈上添加大量数据,并快速填充它。
如何解决:
有几种方法:
- 减少变量的数量,如果递归函数中有局部数组,那就是自找麻烦了。
- 减少函数的参数个数(这里显然不是这样)
- 减少递归调用次数。
如果这还不够,唯一的解决办法就是找到一个迭代解决方案。
【解决方案6】:
我更喜欢迭代。如果你有 16000 个递归步骤,它会非常慢(我认为)。
【解决方案7】:
这是因为您根本没有足够的堆栈空间来递归 16000 次。
递归应该几乎总是比这低得多!否则,重写为循环。您无法通过其他任何方式解决此问题。