5

可能重复:
运行时异常,递归太深

我在开发 ac#.net 程序时遇到问题,我已将其简化为一个简单的问题,我需要了解如果我这样调用函数,为什么这段代码会引发 stackoverflow 异常:

CheckFunc(16000);

但如果我这样称呼它,效果很好

CheckFunc(1000);

这是功能:

private void CheckFunc(Int32 i)
{
    if (i == 0)
        MessageBox.Show("good");
    else
        CheckFunc(i - 1);
}

试图使代码尽可能简单......

我知道有一个堆栈溢出但是哪个堆栈?我怎样才能解决这个问题 ?

谢谢。

4

7 回答 7

12

问题确实是堆栈溢出

为什么会发生:

堆栈是一个特殊的内存区域,其中存储了一些东西:

  • 函数的局部变量
  • 功能参数
  • (最重要的)函数返回地址。当你从一个函数返回时,处理器就是这样知道从哪里返回的。

问题是这个内存区域是有限的。递归调用将在此堆栈上添加大量数据,并快速填充它。

如何修复它:

有几种方法:

  • 减少变量的数量,如果你在递归函数中有一个局部数组,你就是在找麻烦。
  • 减少函数的参数数量(这里显然不是这样)
  • 减少递归调用的次数。

如果这还不够,唯一的解决方案是找到一个迭代解决方案。

于 2012-07-03T13:12:52.537 回答
11

这是因为您根本没有足够的堆栈空间来递归 16000 次。

递归应该几乎总是比这低得多!否则,重写为循环。你不能以任何其他方式解决这个问题。

于 2012-07-03T13:10:47.860 回答
2

您需要了解堆栈,以及它在程序执行中的使用方式。简而言之,您的函数失败是因为它递归调用自身太多次。栈和堆一样,具有有限的大小,但与堆不同的是,它通常要小得多。每次您的函数调用自身时,堆栈中的一些内存用于保存有关函数调用和函数本地变量的信息(在您的示例中对变量 i 的引用)。这称为堆栈帧。当由于递归太深而创建了太多堆栈帧时,就会出现堆栈溢出。

您应该删除 CheckFunc 中的递归,而是从循环中调用它。

于 2012-07-03T13:19:12.600 回答
0

C#中有调用堆栈的限制。在第一种情况下,你超过了这个数字,所以你得到Stack Overflow Exception

没有办法解决这个问题,但你自然可以通过减少递归深度来逃避它。

于 2012-07-03T13:10:53.630 回答
0

我宁愿做它迭代。如果你有 16000 个递归步骤,它会非常慢(我认为)。

于 2012-07-03T13:12:32.707 回答
0

这基本上是因为您对递归的使用是错误的。

溢出的栈是进程的调用栈。堆栈用于发送给方法的参数、调用的返回地址以及方法中的局部变量。

对于每次调用,都会将其添加到堆栈中:

+---------------------+
|                     |
         ...
|                     |
+---------------------+  <-- 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 级。

于 2012-07-03T13:31:20.067 回答
0

调用堆栈溢出。

每当您调用一个函数(或任何编程语言所称的“子例程”、“方法”等)时,CPU 都会存储函数完成后它必须返回的位置的地址。此外,“本地”变量或参数通常也存储在堆栈中。

该堆栈有一个固定的大小,通常可以通过一些魔法来增加——当你创建一个线程时,你通常可以提供一个堆栈大小,当你链接一个程序时,可能还有一个链接器选项来设置堆栈大小。

基本上,您的代码在堆栈上创建了 16000 个“i”副本,以及 16000 个返回指针,以及编译器在函数调用时可能存储在堆栈中的任何内容。在您的另一次尝试中,您只制作了 1000 个这些东西的副本。

当然,您正在做一些称为“尾递归”的事情,它应该被优化掉;不要问我为什么你的编译器不这样做。

于 2012-07-03T13:19:23.973 回答