RAII 和堆栈展开

发布于 2024-10-30 05:08:37 字数 497 浏览 3 评论 0原文

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

然而,编写这个测试程序并随后偶然发现这篇文章/文档,让我意识到堆栈展开只会导致启用 RAII 的资源释放在 try 块内自动启动,而不是在外部/其他范围内自动启动。

我的这个(新)理解正确吗?或者还有哪些细微差别我还没有掌握?有高手愿意插话吗?指向任何好的文章/分析/解释(堆栈展开)的指针都会有帮助/赞赏......

TIL that my notions of the 'inter-twining' (for the lack of a better word) of RAII & stack-unwinding are/were quite(if not completely) wrong. My understanding was that using RAII, guarded against any/all resource leaks - even ones potentially caused by unhandled exceptions.

However writing this test program and subsequently stumbling upon this article/documentation, made me realize that stack unwinding would only cause the RAII-enabled resource deallocation to kick in for automatic's within the try block as opposed to automatic's in, say, outer/other scopes.

Am I correct in this (new) understanding? Or are there further nuances I am yet not grasping? Any gurus out there want to chime in? Pointers to any good write-ups/analyses/explanations (of stack-unwinding) would be helpful/appreciated…

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

王权女流氓 2024-11-06 05:08:37

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

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

§15.5.1/1:

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

§15.5.1/2:

在这种情况下,

    无效终止();

称为 (18.6.3)。在没有找到匹配处理程序的情况下,在调用 Terminate() 之前是否展开堆栈是由实现定义的。在所有其他情况下,在调用 Terminate() 之前不得展开堆栈。不允许实现根据展开过程最终将导致调用 Terminate() 的确定来提前完成堆栈展开。

From the C++03 standard, §15.3/9:

If no matching handler is found in a program, the function terminate() is called; whether or not the stack is unwound before this call to terminate() is implementation-defined (15.5.1).

§15.5.1/1:

In the following situations exception handling must be abandoned for less subtle error handling techniques: ... when the exception handling mechanism cannot find a handler for a thrown exception (15.3) ...

§15.5.1/2:

In such cases,

    void terminate();

is called (18.6.3). In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before terminate() is called. In all other situations, the stack shall not be unwound before terminate() is called. An implementation is not permitted to finish stack unwinding prematurely based on a determination that the unwind process will eventually cause a call to terminate().

别靠近我心 2024-11-06 05:08:37

你是对的,“堆栈展开”发生在从 throw some_exceptioncatch(some_exception) 的过程中。如果你的异常从未到达 catch,我们不知道会发生什么。

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

You are right that "stack unwinding" happens on the way from the throw some_exception to catch(some_exception). If your exception never reaches a catch, we don`t known what happens.

Is that a big problem? As you have shown yourself, you just have to add a catch(...) somewhere to catch all possible exceptions, and the problem goes away.

青丝拂面 2024-11-06 05:08:37

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

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

同样相关的是 中的 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() 规范一样,这意味着即使它们没有析构函数,也不能“过去”抛出异常。叫。)

The Standard defines three ways to end execution of a C++ program:

  • Return from main. Objects with automatic storage (function-local) have already been destroyed. Objects with static storage (global, class-static, function-static) will be destroyed.
  • std::exit from <cstdlib>. Objects with automatic storage are NOT destroyed. Objects with static storage will be destroyed.
  • std::abort from <cstdlib>. Objects with automatic and static storage are NOT destroyed.

Also relevant is std::terminate from <exception>. The behavior of terminate can be replaced using std::set_terminate, but terminate must always "terminate execution of the program" by calling abort or some similar implementation-specific alternative. The default is just { std::abort(); }.

C++ will call std::terminate whenever an exception is thrown and C++ can't reasonably do stack unwinding. For example, an exception from a destructor called by stack unwinding or an exception from a static storage object constructor or destructor. In these cases, there is no (more) stack unwinding done.

C++ will also call std::terminate when a matching catch handler is not found. In this single case, C++ may optionally unwind to main before calling terminate. So your example might have different results with a different compiler.

So if you use RAII correctly, the remaining steps to "leak-proof" your program are:

  • Avoid std::abort.
  • Either avoid std::exit or avoid all objects with static storage duration.
  • Put a catch (...) handler in main, and make sure no allocations or exceptions happen in or after it.
  • Avoid the other programming errors that can cause std::terminate.
    • (On some implementations, functions compiled with a C compiler act like they have C++'s empty throw() specification, meaning that exceptions cannot be thrown "past" them even though they have no destructors to be called.)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文