8

据我所知,没有根本原因StackOverflowException应该是无法捕获的。然而它确实如此。

线程堆栈的最大大小默认为 1MB 或 4MB,具体取决于位数,但该空间仅保留。在实际需要那么多堆栈之前,它不会被提交(因此只使用虚拟地址空间)。

据我所知,为什么它无法捕获背后的基本想法是,到那时你已经用完了所有堆栈,因此除了为堆栈外条件精心设计的代码之外,无法真正执行任何东西但这需要一个明显的解决方案:在我们用完所有堆栈空间StackOverflowException 之前抛出!

为什么不这样做?我能想到的唯一远程合理的原因是,决定这将消耗的额外虚拟地址空间(不是真正的内存使用,请注意!)不值得让这个异常可捕获。

还有其他我没有考虑的问题吗?

我觉得我不得不问,因为大多数解决这个问题的答案似乎都暗示不可能让它正常工作,就是.NET 让它们无法捕获的原因。


可捕获变体的确切实现细节似乎并不重要,但这只是一个想法。首先,将默认保留堆栈大小加倍,但将异常设置为在 1MB 使用时触发。到目前为止,这与现有方法完全相同。但是当达到阈值时,不要抛出无法捕获的异常,而是抛出一个可捕获的异常,同时将阈值增加到 1.5MB。如果捕获到异常并且我们再次突破限制,请将其设置为 1.75MB。然后是 1.875MB。等等。每个嵌套和处理的异常都会获得越来越少的额外堆栈空间来处理,直到我们接近 2MB 以要求抛出无法捕获的变体。

为了在成功处理 StackOverflowException 后降低阈值,让我们将内存页面标记为我们刚刚通过的堆栈大小的一半(在第一个实例中为 0.5MB)以在写入时出错。当我们回到堆栈使用水平时,故障处理程序将几乎完全被触发,因此它并不昂贵。处理程序将检查实际堆栈大小并在适当时将阈值放回原处。

4

1 回答 1

4

我不知道为什么它从 1.1 更改为 2.0(希望 Eric 有时会发表评论),但我怀疑是因为这样的情况:

public bool Count(int64 i)
{
    try
    {
        // somewhere in here a stack overflow exception is thrown
        if(i < int64.MaxValue)
            return Count(i+1);
        else
            return true;
    } Catch(StackOverflowException soEx)
    {
        HandleError(soEx);
    }
}

public void HandleError(Exception ex)
{
    // error handling code here
}

你实际上也会在你的行中抛出一个异常,HandleError(soEx);因为它在堆栈太满之前停止了。

我怀疑这就是他们选择使堆栈溢出异常无法捕获的原因,因为您无法捕获它然后调用该捕获块中的任何方法。

至于你的建议,他们不希望有一个堆栈大小和一个有效的堆栈大小,因为这会使事情复杂化,我怀疑他们也不希望有一个可变的堆栈大小。这些事情中的任何一个都会使框架过于复杂,并且只提供部分解决方案,为了争论,如果你的代码是:

public void HandleError(Exception ex)
{
    try
    {
        HandleError(soEx);
    } Catch(StackOverflowException soEx)
    {
        HandleError(soEx);
    }
}

使用变量堆栈并且无法真正防止 StackOverflowException 被捕获,这会导致其他东西崩溃。

所以让它更容易和更可预测的是它只是抛出并且无法捕捉。

编辑:这个答案指出了您仍然可以在 CLR 2.0+ 中捕获 StackOverflowExceptions的唯一情况

于 2012-05-30T12:06:14.373 回答