为什么在代码下运行时会出现stackoverflow错误?

时间:2018-04-06 07:07:17

标签: c# .net static stack-overflow

你能告诉我为什么下面的代码会抛出堆栈溢出错误吗?

 class Program
    {
        static void Main()
        {
            Program.Main();
        }
    }

为什么调用Main()方法会导致堆栈内存填满并最终导致堆栈溢出错误?当我像这样运行无限循环时,这种情况永远不会发生 -

class Program
    {
        static void Main()
        {
            // Program.Main();
            bool abcd = true;
            while (abcd)
                Console.WriteLine("Running");
        }
    }

如果这与某个类的静态成员的内存管理或与之相关的内容有关,请告诉我。我在互联网上寻找答案但找不到合适的答案。

1 个答案:

答案 0 :(得分:12)

每次调用任何方法 1 (在您的情况下为Main方法)时,都会创建一个新的堆栈帧 - 占用内存。你没有无限的内存(特别是在堆栈上),所以最终你的堆栈空间不足,此时抛出异常。

请注意,您的方法当前没有任何局部变量或参数,因此每个堆栈帧都相对较小......如果您有很多局部变量,它会在较少的调用后抛出异常。

例如:

using System;

class Test
{
    static void Main()
    {
        // Use whichever you want to demonstrate...
        RecurseSmall(1);
        //RecurseLarge(1);
    }

    static void RecurseSmall(int depth)
    {
        Console.WriteLine(depth);
        RecurseSmall(depth + 1);
    }

    static void RecurseLarge(int depth)
    {
        Console.WriteLine(depth);
        RecurseLarge(depth + 1);

        // Create some local variables and try to avoid them being 
        // optimized away... We'll never actually reach this code, but
        // the compiler and JIT compiler don't know that, so they still
        // need to allocate stack space.
        long x = 10L + depth;
        long y = 20L + depth;
        decimal dx = x * depth + y;
        decimal dy = x + y * depth;
        Console.WriteLine(dx + dy);
    }
}

在我的机器上(使用默认编译选项),RecurseSmall打印到深度21175; RecurseLarge打印到深度4540。

JIT编译器还能够检测某些情况,它可以使用所谓的tail recursion来替换现有的堆栈帧,并在此时有效地调用< em>不需要更多的堆栈空间。在我的机器上,如果用以下代码编译上面的代码:

csc /o+ /debug- /platform:x64

...它永远运行,永远不会占用太多的堆栈空间。我通常认为依赖这个优化是一个坏主意,因为它很难预测(并且肯定取决于你正在使用的确切JIT)。

将所有这些与while循环进行比较,Console.WriteLine循环使用Invariant所需的额外堆栈空间,但每次调用后都会回收堆栈空间,因此永远不会耗尽空间。< / p>

1 逻辑上,至少。有时可以通过JIT编译器内联方法,这将避免这种情况。