【问题标题】:RAII and Stack unwindingRAII 和堆栈展开
【发布时间】:2011-04-05 19:42:15
【问题描述】:

直到我对 RAII 和堆栈展开的“相互缠绕”(因为没有更好的词)的概念是/完全(如果不是完全)错误的。我的理解是,使用 RAII 可以防止任何/所有资源泄漏——甚至可能是由未处理的异常引起的。

然而,写this test program 并随后偶然发现this article/documentation,让我意识到堆栈展开只会导致启用 RAII 的资源释放在 try 块内启动,而不是自动在外部 /其他范围。

我对这个(新的)理解是否正确?还是我还没有掌握更多的细微差别?有哪位大师想插话吗?任何好的文章/分析/解释(堆栈展开)的指针都会有所帮助/赞赏......

【问题讨论】:

标签: c++ exception exception-handling raii stack-unwinding


【解决方案1】:

来自 C++03 标准,§15.3/9:

如果在程序中没有找到匹配的处理程序,则调用函数 terminate();在调用 terminate() 之前是否展开堆栈是实现定义的 (15.5.1)。

§15.5.1/1:

在以下情况下,必须放弃异常处理以使用不太微妙的错误处理技术:...当异常处理机制找不到抛出异常的处理程序时(15.3)...

§15.5.1/2:

在这种情况下,

    void terminate();

被称为 (18.6.3)。在没有找到匹配处理程序的情况下,在调用 terminate() 之前是否展开堆栈是实现定义的。在所有其他情况下,在调用 terminate() 之前不应展开堆栈。基于展开过程最终会导致调用 terminate() 的确定,不允许实现提前完成堆栈展开。

【讨论】:

  • 此文档在线吗?如果是这样,您可以添加指向您引用的链接的链接吗?谢谢。 (我的搜索能力不及格,而且我似乎无法找到它。)
  • @decimus phostle :这是一个 ISO 标准,所以不,不是免费的,但你可以在这里购买:iso.org/iso/catalogue_detail.htm?csnumber=38110。或者,您可以免费阅读 C++0x 草案,最新版本在这里:open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf。但是,请记住,C++0x 中有 许多许多 项更改,因此其中的大部分内容仅适用于前沿编译器。
  • @decimus:当前标准的全称是ISO/IEC 14882:2003(E)。去谷歌上查询。或者正如 ildjarn 所说,期待下一个版本。它现在任何一个月都会被批准,但还没有人完全实施它。
【解决方案2】:

你说得对,“堆栈展开”发生在从throw some_exceptioncatch(some_exception) 的过程中。如果您的异常从未被捕获,我们不知道会发生什么。

这是个大问题吗?正如您向自己展示的那样,您只需在某处添加catch(...) 即可捕获所有可能的异常,问题就会消失。

【讨论】:

  • 如果没有捕获到异常会发生什么,这不是未知的。只需阅读 ildjarn 的回复即可。
  • 程序当然会终止,但不知道是否有任何“堆栈展开”首先发生。这很容易通过添加 catch(...) 来解决。
  • 比“未知”更准确:当抛出的异常没有匹配的catch时,在调用terminate之前堆栈是否展开由实现定义。
【解决方案3】:

标准定义了结束 C++ 程序执行的三种方式:

  • main 返回。具有自动存储(功能本地)的对象已被销毁。具有静态存储(全局、静态类、静态函数)的对象将被销毁。
  • std::exit 来自 <cstdlib>。具有自动存储功能的对象不会被销毁。具有静态存储的对象将被销毁。
  • std::abort 来自 <cstdlib>。具有自动和静态存储的对象不会被销毁。

同样相关的是来自<exception>std::terminateterminate 的行为可以使用std::set_terminate 替换,但terminate 必须始终通过调用abort 或一些类似的特定于实现的替代方法来“终止程序的执行”。默认只是{ std::abort(); }

每当抛出异常并且 C++ 无法合理地进行堆栈展开时,C++ 都会调用 std::terminate。例如,堆栈展开调用的析构函数的异常或静态存储对象构造函数或析构函数的异常。在这些情况下,没有(更多)堆栈展开。

当找不到匹配的catch 处理程序时,C++ 也会调用std::terminate。在这种情况下,C++ 可以可选地在调用terminate 之前展开到main。因此,您的示例使用不同的编译器可能会产生不同的结果。

因此,如果您正确使用 RAII,“防泄漏”程序的其余步骤是:

  • 避免std::abort
  • 要么避免std::exit,要么避免所有具有静态存储持续时间的对象。
  • catch (...) 处理程序放在main 中,并确保在其内部或之后不会发生分配或异常。
  • 避免可能导致std::terminate 的其他编程错误。
    • (在某些实现中,使用 C 编译器编译的函数的行为就像它们具有 C++ 的空 throw() 规范,这意味着即使它们没有要调用的析构函数,也不能“过去”抛出异常。)

【讨论】:

    猜你喜欢
    • 2016-11-03
    • 2014-04-17
    • 2017-10-19
    • 1970-01-01
    • 1970-01-01
    • 2011-07-10
    • 2018-04-01
    • 2011-12-13
    • 2011-01-20
    相关资源
    最近更新 更多