Try/Finally实际上是异常安全的

本文关键字:异常 安全 实际上 Finally Try | 更新日期: 2023-09-27 17:58:19

假设您有一段代码,如:

resource = allocateResource();
try { /* dangerous code here  */ }
finally { free(resource); }

我在这里不是指任何特定的语言,但我想Java、C#和C++将是很好的例子(假设您在MSVC++中使用__try/__finally)。

这个例外安全吗

就我个人而言,我不认为这是异常安全的,因为如果在进入try块之前出现异常怎么办?然后你的资源就会泄漏。

不过,我已经看了足够多次了,我想我错过了一些东西。。。是吗?或者这真的不安全吗?


编辑:

我不是在问allocateResource抛出异常,而是在函数返回之后,但在分配resource之前,出现异常的情况。

Try/Finally实际上是异常安全的

我问的不是allocateResource抛出异常,而是一种情况其中在该函数已返回,但在分配资源之前。

尝试处理异常安全的这一方面会变得非常混乱,尤其是因为语言构造不允许在赋值语句的中间安装finally处理程序。

我对这一切的基本原理是,如果你不能从函数调用的末尾开始为变量赋值,那么你的系统就已经被冲洗掉了。当你不能分配给一个变量时,谁在乎你是否泄露了内存?

重点是让所有可以在try块内抛出异常的代码。在您的情况下:

try
{
    resource = allocateResource();
    //...
}
finally { free(resource); }

否则——不,当然不安全。

在C#的情况下,它被认为是不安全的,因为可以在资源分配和try块的开头之间抛出ThreadAbortException。出于这个原因,C#4更改了using块的扩展,以在try中移动资源分配,而finally块使用隐藏的布尔值(或对null进行测试——我记不清了)来确定分配是否真的发生了。

这取决于allocateResource的编写方式。给定上面的片段,allocateResource可能会导致两种结果:1) 它分配并返回资源2) 它超出(因此不返回资源)

因此,如果allocateResource在抛出之前确定不会在内部泄露任何分配,那么上述方法不会泄露resource,因为该方法不能同时抛出和返回。

只有try{}块内的代码是安全的。并且只有在所有异常都被正确捕获的情况下。

当然,块外的代码不会,这正是所需的行为。

请注意,finally{}块中的代码也可能引发异常,因此您可能需要在finally或catch块中包含try-catch块。

例如:

try {
    // your code here
} finally {
    try {
        // if the code in finally can throw another exception, you need to catch it inside it
    } catch (Exception e) {
       // probably not much to do besides telling why it failed
    }
} catch (Exception e) {
    try {
        // your error handling routine here
    } catch (Exception e) {
       // probably not much to do besides telling why it failed
    }
}
  • 如果异常在分配之前抛出,则没有任何可释放的内容,因此没有任何可泄漏的内容
  • 如果异常发生在分配之后的try/catch块内部的<em],则将由finally处理>
  • 如果异常可能发生在分配之后和try/catch块之前,则应重新考虑代码,并将那些有问题的行移到块内

我认为你在回答自己的问题。如果allocateResource在资源被分配给变量之前分配并抛出异常,那么资源就会泄漏,并且try/finally块对此无能为力,因为大多数语言(包括Java和C#)中的try/finally块实际上并不知道您在其中使用的资源。

因此,为了使try/finally有效,allocateResource必须以某种方式保证是原子;要么分配并赋值给变量而不失败,要么根本不分配而失败。由于没有这样的保证(特别是考虑到不可预测的线程死亡),因此try/finally块不能有效地安全。

一些语言开始支持usingwith子句,知道关于资源的信息,因此能够安全地关闭它们(尽管这取决于解释器/编译器/运行时等的实现)

在C#中,任何托管和未引用的资源都将在下一次运行中被垃圾收集,因此您没有问题。