如何避免堆栈溢出?

我使用CSharpCodeProvider编译我的代码,并在结果汇编中动态创建某个类的实例。 比我叫一些方法。 如果方法有递归,我得到StackOverflowException,我的应用程序终止。

我该如何避免这种情况?

using System; using System.Runtime.Remoting; namespace TestStackOverflow { class Program { class StackOver : MarshalByRefObject { public void Run() { Run(); } } static void Main(string[] args) { AppDomain domain = AppDomain.CreateDomain("new"); ObjectHandle handle = domain.CreateInstance(typeof (StackOver).Assembly.FullName, typeof (StackOver).FullName); if (handle != null) { StackOver stack = (StackOver) handle.Unwrap(); stack.Run(); } } } } 

有关:

什么是堆栈溢出?

StackOverflow表示您的递归过深,堆栈内存不足。 例如:

 public class StackOver { public void Run() { Run(); } } 

这将导致堆栈溢出,因为StackOver :: Run()将被反复调用,直到没有内存为止。

我怀疑在你的情况下,你可能会错过终止条件或者你正在运行太多的递归迭代。

如果您尝试保持应用程序运行,请尝试:

 namespace TestStackOverflow { class Program { class StackOver : MarshalByRefObject { public bool Run() { return true; // Keep the application running. (Return false to quit) } } static void Main(string[] args) { // Other code... while (stack.Run()); } } } 

Run正在调用Run。 那就是无限递归。

  class StackOver : MarshalByRefObject { public void Run() { Run(); // Recursive call with no termination } } 

如果递归导致堆栈溢出,那么问题与编译类无关 – 递归函数需要终止条件,因为C#不(通常)优化尾调用 。

避免使用递归函数的堆栈溢出的唯一方法是有一个明确的退出条件,无论输入如何,最终都会满足。 您可以定义最大深度并在到达时停止进行递归调用,或者确保您检查的数据是有限的(并且在合理的限制范围内),或两者的组合。

我没有CSharpCodeProvider的好背景,但我知道递归实现的算法可以通过循环实现

好。 使用CSharpCodeProvider无关紧要。 我正在另一个域中使用Reflection加载程序集。 我认为域名是出于安全原因而创建的。 如何保护应用程序免于终止???

 using System; using System.Runtime.Remoting; namespace TestStackOverflow { class Program { class StackOver : MarshalByRefObject { public void Run() { Run(); } } static void Main(string[] args) { AppDomain domain = AppDomain.CreateDomain("new"); ObjectHandle handle = domain.CreateInstance(typeof (StackOver).Assembly.FullName, typeof (StackOver).FullName); if (handle != null) { StackOver stack = (StackOver) handle.Unwrap(); stack.Run(); } } } } 

每次从方法栏调用方法foo时,bar都会添加到调用堆栈中。 调用堆栈用于在调用方法之前跟踪代码的位置,以便在foo完成时返回到那里。

以下递归函数

 int Factorial(int n) { if (n == 0) { return 1; } return n * Factorial(n - 1); } 

在几次递归调用Factorial(5)后,调用堆栈看起来像这样:

 Factorial(5) -> Factorial(4) -> Factorial(3) -> Factorial(2) -> Factorial(1) 

此时n为1,因此该函数停止调用递归情况,而是返回1.程序然后开始回调调用堆栈,整个事件返回120。

没有调用堆栈,程序在完成执行方法时将不知道返回的位置。

现在假设基本情况不存在,它看起来像这样:

 int Factorial(int n) { return n * Factorial(n - 1); } 

在调用Factorial(5)的几次递归之后,调用堆栈将如下所示:

 Factorial(5) -> Factorial(4) -> Factorial(3) -> Factorial(2) -> Factorial(1) -> Factorial(0) -> Factorial(-1) -> Factorial(-2) -> Factorial(-3) -> Factorial(-4) -> Factorial(-5) -> Factorial(-6) -> Factorial(-7) -> Factorial(-8) -> Factorial(-9) -> Factorial(-10) -> Factorial(-11) -> Factorial(-12) -> Factorial(-13) -> Factorial(-14) -> Factorial(-15) etc… 

因为没有任何一点代码停止调用自身它将永远继续,并且调用堆栈将增长并增长并增长占用越来越多的内存,直到它超过它已分配的内存并抛出StackOverflowexception。

有两种方法可以阻止这种情况发生,最好的方法取决于具体情况。

1提供基本案例。 确保最终达到的某些条件会阻止函数调用自身。 在Factorial的情况下,它是n == 1,但它可能已经过了一定的时间,它已经递归了一定次数,某些计算的某些结果在某些范围内,无论如何。 只要它在堆栈太大之前停止重复使用。

2删除递归并重新写入。 任何递归算法都可以重写为非递归算法。 它可能不那么干净和优雅,但它可以做到。 在阶乘论证中,它可能是这样的:

 int Factorial(int n) { int result = 1; for (int i = 0; i < n; i += 1) { result *= n; } return result; } 

如果目标是一次又一次地连续运行相同的函数,那么您可以重新编写递归

 void Foo() { // Some code Foo(); } 

 void Foo() { while (true) { // Some code } }