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

Why does stackoverflow error comes when running below code?

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

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

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

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

请告诉我这是否与 class 的静态成员的内存管理有关或与之相关。我在网上找过答案,但没找到合适的答案。

每次你调用任何方法1(在你的例子中是Main方法),都会创建一个新的堆栈框架——占用内存。你没有无限量的内存(特别是在堆栈上),所以最终你 运行 出栈 space,此时抛出异常。

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

例如:

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 用新的堆栈框架替换现有的堆栈框架,此时方法调用有效 不需要 需要更多堆栈 space。在我的机器上,如果你编译上面的代码:

csc /o+ /debug- /platform:x64

...它 运行 永远存在,并且永远不会使用太多堆栈 space。不过,我通常认为 依赖 这种优化是个坏主意,因为它很难预测(并且绝对取决于您使用的确切 JIT)。

将所有这些与您的 while 循环进行比较,该循环使用 Console.WriteLine 所需的任何额外堆栈 space,但该堆栈 space 在每次调用后都会被回收, 所以你永远 运行 出 space.


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