F#作为一种语言非常适合编写语言解释器或编译器,但是,有一件事情让我们不知所措:StackOverflowException。
众所周知,无法捕获SO异常,无法从中恢复。防止这种异常的一种显而易见的技术是在你进行时计算堆栈的深度。开销,是的,但可行,也许并非每项功能都必要。
使用F#,这项技术并没有带来太多好处。我们在解释器的动态生成表达式中大量使用尾调用优化技术。我们在SO异常中遇到的问题是:
只是增加堆栈大小不会有足够的帮助,我们希望给用户一个可记录的错误,最好是由调用应用程序捕获。为此,我们需要能够手动抛出异常,这使它可以捕获。但我们如何确定合适的时机?
更新:
Hans Passant在这里正确地提出了可预测性。但是,使用此DSL的程序员期望(某些)调用获得TCO,因此他们不希望有强大的堆栈限制。他们知道自己在做什么。尽管如此,他们的程序需要能够优雅地死掉,至少在任何调用应用程序(即使用我们的库的C#程序)的范围内。
没有伤害。
答案 0 :(得分:5)
我不熟悉F#,但我确实在C#中编写了一个ECMAScript-262 v.5解释器,所以我可以解决你的一些问题。如您所知,自v2.0起,无法在.NET应用程序中捕获StackOverFlowException。虽然有一个相当可靠的解决方法,而且速度很快。
如果在堆栈上声明一个变量,例如int,则该变量的地址代表堆栈的顶部,并让您知道剩余多少空间。因此,如果您在启动时记录该变量,而堆栈基本上是空的,则每次输入新的执行上下文时都可以引用它。
以下是我的口译员解决这个问题的一些例子。
C#:
这些是在主Interpreter类中声明的静态变量。
private static int TopOfStack;
private const int STACK_SIZE = 1000000;
这是主要的Interpreter类的静态构造函数。
static Interpreter() {
InitializeGlobalEnvironment();
//---------------------------------------------------
// Get the address of a new variable allocated on the stack
// to represent the amount of memory available. Record
// the address.
//---------------------------------------------------
int stackVariable;
TopOfStack = (int)&stackVariable;
}
在解释ECMAScript函数之前调用此代码。如果新堆栈分配变量的地址小于short.Max,则抛出可捕获异常。您需要为调用堆栈留出一些空间来放松。
internal static ExecutionContext EnterFunctionContext(IValue thisArg, LIST args, FUNCTION function) {
...
LexicalEnvironment localEnv = ECMA.NewDeclarativeEnvironment(function.Scope);
ExecutionContext context = new ExecutionContext() {
Strict = function.IsStrict,
VariableEnvironment = localEnv,
LexicalEnvironment = localEnv
};
int remainingStackSpace;
if (STACK_SIZE - (TopOfStack - (int)&remainingStackSpace) < short.MaxValue)
throw new ECMARuntimeException("stack overflow", RuntimeErrorType.RangeError);
CallStack.Push(context);
LexicalEnvironment env = CurrentContext.VariableEnvironment;
...
}
解释以下代码时,会在迭代1200周围抛出异常。
更新:在发布版本中,它大约有4100次迭代。
的ECMAScript:
RecursiveCall(0);
function RecursiveCall(counter){
return RecursiveCall(++counter);
}
输出:RangeError: stack overflow
您可以使用Thread(ParameterizedThreadStart, Int32)
构造函数增加Thread中的堆栈大小。我只是觉得不需要。
祝你的项目好运。我希望这会有所帮助。