C++标准是否保证使用异常进行堆栈展开?

20

关于堆栈展开,C++标准说:

在完成异常对象的初始化 ([except.throw]) 直到完成异常处理程序的激活([except.handle])期间,异常被视为未处理。这包括堆栈展开。

参见当前标准的第15.5.3节。我试图理解最后一句话 (This includes stack unwinding.) 所指的是什么:

  • 它意味着编译器必须负责展开堆栈吗?
  • 还是说展开堆栈是否进行取决于编译器?

这个问题源于以下代码片段:

#include <iostream>
#include <exception>

struct S{
    S() { std::cout << " S constructor" << std::endl; }
    virtual ~S() { std::cout << " S destructor" << std::endl; }
};

void f() {
    try{throw(0);}
    catch(...){}
}

void g() {
    throw(10);
}

int main() {
    S s;
    f();
    //g();
}

现在:

  1. 如果按照原样运行它(捕获异常),则会给出堆栈展开的提示。
  2. 如果将f();注释掉并取消注释g();(不捕获异常),则可以得到堆栈未被展开的提示。

因此,这两个实验似乎支持上面的第一个要点;clang++和g++都对结果达成了一致意见(但这并不是一个判别标准)。

另外,我觉得很奇怪的是,标准非常小心地指定了对象的“存活时间”和“持续时间”,却在这里留下了一个阴影。

是否有人可以澄清?未捕获异常的堆栈展开是否得到了标准的保证?如果是,那在哪里?如果不是,为什么?


2
在发布的程序中,在堆栈展开期间s并没有被销毁。它会在f()返回后被销毁。 - aschepler
1个回答

32
标准是否保证未捕获异常的堆栈展开?
标准只保证对于已捕获的异常进行堆栈展开([except.handle]/9):
如果没有找到匹配的处理程序,则会调用函数std::terminate(); 在调用std::terminate()之前是否展开堆栈由实现定义。否则就是实现定义。
如果没有展开堆栈,为什么?
在发生未捕获的异常时,标准导致调用std::terminate,表示程序执行结束。 如果您有某些特定于平台的方式记录系统在那个时间的状态信息,则可能不希望该状态受到堆栈展开的干扰。
如果你不这样做...那么你无论如何都不关心。
如果您真的需要始终展开堆栈,则可以将您的main代码 (以及任何线程函数) 放入try {} catch(...) {throw;}块中。

如果您确实需要堆栈始终处于未解开状态,那么可以将主要代码放在try {} catch(...) {throw;}块中。那么线程呢?我有直觉认为在这种情况下,各个线程入口函数也需要try-catch块。 - Johann Gerell
@JohannGerell:我认为如果线程函数以异常退出,那就是未定义行为(UB)。 - Kerrek SB
1
@KerrekSB 不是“终止”,而是“终止”。 - T.C.
我的程序经常以类似于 int main2(); int main() { try { return main2(); } catch(...) { global_handler(); } } 的东西开始。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接