Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
.net 防止语言解释器中的堆栈溢出_.net_Compiler Construction_F#_Stack Overflow_Tail Recursion - Fatal编程技术网

.net 防止语言解释器中的堆栈溢出

.net 防止语言解释器中的堆栈溢出,.net,compiler-construction,f#,stack-overflow,tail-recursion,.net,Compiler Construction,F#,Stack Overflow,Tail Recursion,F#作为一种非常适合编写语言解释器或编译器的语言,有一件事一直在我们不想要它的地方困扰着我们:StackOverflowException 众所周知,SO异常无法捕获,也无法从中恢复。防止这种异常的一种明显的方法是在进行过程中计算堆栈的深度。开销,是的,但在每个函数中都是可行的,也许不是必需的 然而,对于F#,这种技术并没有带来多少好处。我们在解释器的动态生成表达式中大量使用尾部调用优化技术。我们面临的例外问题包括: 我们如何才能将它们告知用户,而不是破坏当前整个AppDomain 如果我们计

F#作为一种非常适合编写语言解释器或编译器的语言,有一件事一直在我们不想要它的地方困扰着我们:StackOverflowException

众所周知,SO异常无法捕获,也无法从中恢复。防止这种异常的一种明显的方法是在进行过程中计算堆栈的深度。开销,是的,但在每个函数中都是可行的,也许不是必需的

然而,对于F#,这种技术并没有带来多少好处。我们在解释器的动态生成表达式中大量使用尾部调用优化技术。我们面临的例外问题包括:

  • 我们如何才能将它们告知用户,而不是破坏当前整个AppDomain
  • 如果我们计算堆栈深度,我们如何知道一个函数是TCO'ed还是内联的,这样我们就不必计算了
  • 如果我们采用另一种方法(如以给定的深度间隔检查堆栈本身),是否有任何(已知)方法可以在不严重影响性能的情况下做到这一点
仅仅增加堆栈大小是不够的,我们希望给用户一个可记录的错误,最好由调用应用程序捕获。为此,我们需要能够手动抛出异常,从而使其可捕获。但我们如何确定正确的时机呢

更新
汉斯·帕桑在这里正确地提出了可预测性。然而,使用此DSL的程序员希望(某些)调用得到TCO'ed,因此他们不希望有强堆栈限制。他们知道自己在做什么。尽管如此,他们的程序仍然需要能够优雅地消亡,至少达到任何调用应用程序(即使用我们的库的C#程序)的程度 没有受到伤害。

我对F不熟悉,但我确实用C语言编写了一个ECMAScript-262 v.5解释器,因此我可以与您的一些问题联系起来。正如您所知,自v2.0以来,.NET应用程序中无法捕获StackOverFlowException。不过有一个相当可靠的解决方法,而且速度很快

如果在堆栈上声明一个变量,例如int,则该变量的地址表示堆栈的顶部,并让您知道还有多少空间。因此,如果在堆栈基本为空的情况下在启动时记录该变量,则可以在每次输入新的执行上下文时引用它

下面是我的翻译针对这个问题的一些练习

C#:

这些是在主解释器类中声明的静态变量

private static int TopOfStack;
private const int STACK_SIZE = 1000000;
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;
}
这是主解释器类的静态构造函数

private static int TopOfStack;
private const int STACK_SIZE = 1000000;
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;
...
}
输出:
RangeError:堆栈溢出

您可以使用
线程(ParameterizedThreadStart,Int32)
构造函数增加线程中的堆栈大小。我只是觉得没必要


祝你的项目好运。我希望这能有所帮助。

@HansPassant,谢谢你的参与。我们曾想过用这种方式解决这个问题,但感觉就像用大锤解决一个小问题,我们甚至还没有尝试过。此外,我们的编译器应该可以在宿主语言中作为库调用,并且每次编译一个小片段时都会生成一个进程,这实在是太大的开销了。这里的问题是可预测性。DSL可能包含许多会被TCO’ed的函数,设置一个硬限制(我们目前拥有的)似乎过于严格,因为我们无法以任何可以想象的方式计算该限制。@HansPassant:我们的程序将另一种语言翻译成编译表达式,然后针对变量输入运行它。为了使事情更加复杂,语言定义包含一个求值函数。我不知道为什么,增加堆栈大小只能在一定程度上起作用,但在达到一定阈值后,它会使程序的速度达到蜗牛般的速度。首先,我们的程序将从IDE(不是我们的)调用,因为我们有一个SO,所以关闭IDE将是灾难性的。AppDomains过去有一种解决方法,但我认为这已经不可能了。为什么不重写解释器,这样它就不会泄漏堆栈空间呢?“我们不能重写所有可能的表达式,结果只包含连续性”。我不明白为什么那是不可能的。你能发布一些代码或一个具体的例子来强调为什么这是不可能的吗?