你能告诉我为什么下面的代码会抛出堆栈溢出错误吗?
class Program
{
static void Main()
{
Program.Main();
}
}
为什么调用Main()方法会导致堆栈内存填满并最终导致堆栈溢出错误?当我像这样运行无限循环时,这种情况永远不会发生 -
class Program
{
static void Main()
{
// Program.Main();
bool abcd = true;
while (abcd)
Console.WriteLine("Running");
}
}
如果这与某个类的静态成员的内存管理或与之相关的内容有关,请告诉我。我在互联网上寻找答案但找不到合适的答案。
答案 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编译器内联方法,这将避免这种情况。