【问题标题】:Why does stackoverflow error comes when running below code? [closed]为什么在代码下面运行时会出现stackoverflow错误? [关闭]
【发布时间】:2018-04-06 07:07:17
【问题描述】:

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

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

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

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

请让我知道这是否与类的静态成员的内存管理有关或与之相关。我在互联网上寻找答案,但找不到合适的答案。

【问题讨论】:

  • 无限递归调用——堆积堆栈
  • 在第一个你从来没有调用过 program.main,所以数十亿次迭代之后,你仍然进入 main 并再次进入 main.. while 循环是一个 while 循环,它没有向堆栈添加另一个级别。把它想象成爬楼梯。第一个你在永无止境的跑步机上,第二个你进去站在第一步聊天。
  • 因为你在调用Main,在里面你在调用Main,在里面你在调用Main,在里面......方法调用越来越深,形成一个堆栈,其大小是有限的。一旦超过限制,它就会爆炸。
  • 无限循环与递归。虽然它们可能看起来相似,但它们的作用并不完全相同。递归会消耗堆栈,而无限循环则不会。作为一般规则,永远不要在没有退出条件/检查的情况下进行递归。

标签: c# .net static stack-overflow


【解决方案1】:

每次您调用任何方法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 将现有堆栈帧替换为新堆栈帧,此时方法调用有效不需要更多的堆栈空间。在我的机器上,如果你编译上面的代码:

csc /o+ /debug- /platform:x64

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

将所有这些与您的while 循环进行比较,该循环使用Console.WriteLine 所需的任何额外堆栈空间,但每次调用后都会回收该堆栈空间,因此您永远不会用完空间。


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

【讨论】:

  • 每次调用any方法...
  • @SirRufo:是的;已编辑。
  • 不确定它是否与局部变量的数量有关。
  • @Alexey:是的;我现在正在编写代码来证明这一点。
  • @DaisyShipton 你是对的,我很抱歉
猜你喜欢
  • 2023-01-05
  • 2020-11-11
  • 2015-12-13
  • 1970-01-01
  • 1970-01-01
  • 2020-12-15
  • 2021-06-25
  • 2021-07-30
  • 2020-11-26
相关资源
最近更新 更多