【问题标题】: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 次。

              递归应该几乎总是比这低得多!否则,重写为循环。您无法通过其他任何方式解决此问题。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2016-07-19
                • 1970-01-01
                • 1970-01-01
                • 2015-02-12
                • 1970-01-01
                • 2011-12-26
                • 2018-01-03
                • 1970-01-01
                相关资源
                最近更新 更多