为什么在代码下方 运行 时会出现 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 编译器有时可以内联方法,这将避免这种情况。
你能告诉我为什么下面的代码会抛出堆栈溢出错误吗?
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 编译器有时可以内联方法,这将避免这种情况。