为什么这个递归不会产生StackOverFlowException?

时间:2012-12-29 20:48:24

标签: c# .net recursion stack-overflow csc

此代码有什么问题:

using System;
namespace app1
{
    static class Program
    {
        static int x = 0;
        static void Main()
        {
            fn1();
        }
        static void fn1()
        {
            Console.WriteLine(x++);
            fn1();
        }
    }
}

我使用以下命令编译这段代码:

csc /warn:0 /out:app4noex.exe app4.cs

当我双击exe时,它似乎没有抛出异常(StackOverFlowException),并且一直运行。

使用visual studio命令提示符2010,但我也在系统上安装了vs 2012,这些都是最新的。

4 个答案:

答案 0 :(得分:10)

因为优化器将尾递归调用展开为:

    static void fn1()
    {
      START:

        Console.WriteLine(x++);
        GOTO START;
    }

重写以获得类似的异常:

   static int y;

   static void fn1()
   {
       Console.WriteLine(x++);
       fn1();
       Console.WriteLine(y++);
   }

答案 1 :(得分:3)

x64抖动将此检测为尾部调用并将其优化,而x86抖动不会执行此操作。 x64抖动对这些优化更加积极。请参阅Bart de Smet的analysis和CLR团队的blog post

答案 2 :(得分:1)

有一种称为tail recursive optimization的东西。

从堆栈的角度来看,基本上它意味着如果方法做的最后一件事是调用另一个方法,那么新调用可以采用调用方法的堆栈帧。例如:

static void Main()
{
  fn(0);
}

static void fn(int value)
{
   fn(value+1);
}

而不是调用堆栈增长Main->fn(0)->fn(1)->...令人作呕,它将长度恰好是两个链接,首先是Main->fn(0)而不是Main->fn(1),最多是Main->fn(int.MaxValue)爆炸或溢出。

现在,问题是,C#编译器实际上是这样做的吗? AFAIK,使用4.0及更高版本的C#编译器,在x86环境中编译时,使用尾调用优化,并且在编译x64应用程序时, 使用尾部-call优化(显然,从其他评论/答案我是正确的)。例如。在我的系统上,使用LINQPad,您提供的代码立即被StackOverflowException炸毁。

答案 3 :(得分:-1)

当程序在visual studio环境中运行时,它将使用有限的堆栈深度。我的意思是当你通过在键盘上点击F5和F6来编译VS2012中的程序时,它会向csc.exe程序发送一些参数以限制程序并使堆栈溢出来识别你程序中的错误源代码&算法。 实际上,没有Stack-over-flow错误,程序的进程将使用真实存储和虚拟存储,操作系统将为您完成。

注意:它也与你的操作系统有关,如果内存管理和cpu调度存在缺陷,某些操作系统会抛出错误。