【问题标题】:Why is stack unwinding guaranteed only for handled exceptions?为什么仅对处理的异常保证堆栈展开?
【发布时间】:2017-12-06 19:48:55
【问题描述】:

C++ standard 说 ([except.handle]/9):

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

例如,下面代码的行为(是否打印S::~S())是实现定义的:

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

int main() {
    S s;
    throw std::runtime_error("exception");
}

我想深入了解:为什么要定义实现?如果未捕获异常(类似于顶级函数中的try{ ... } catch(...) { throw; }),为什么在调用std::terminate() 之前不能将上下文展开到其条目?乍一看,这种行为与 RAII 一致,更清晰、更安全。

【问题讨论】:

    标签: c++ exception


    【解决方案1】:

    如果没有捕获到异常,则调用std::terminate。我们失败得太厉害了,主机环境需要介入并(也许)在我们之后进行清理。在这种情况下展开堆栈就像给神风敢死队飞行员戴头盔一样。

    因此,对于托管环境,什么都不做而让主机清理可能更有意义。

    现在,如果您在一个独立的实现中,并且正在抛出异常,那么没有人可以在您之后进行清理。在这种情况下,实现应该执行堆栈展开,因为这应该是为了清理混乱。

    标准将其留给实现,以促进这两种截然不同的执行环境。


    就像@Matteo 指出的那样,std::terminate 在任何潜在的展开之前被调用,因为您可以为它设置一个处理程序。只要堆栈尚未展开,该处理程序就可以对堆栈状态做一些有用的事情。

    【讨论】:

    • “给神风敢死队飞行员戴头盔”。这对我来说是一个新的。
    • @AndyG - 我不能相信。在亚历山德雷斯库的一次谈话中听到了。这是关于noexcept,但原则仍然存在:)
    • 另外,如果您设置一个可以记录当前异常及其堆栈跟踪的自定义终止处理程序,展开之前调用std::terminate 非常方便。
    • @MatteoItalia 请注意,在调用 std::terminate() 之前堆栈是否展开是实现定义并且不保证
    • @StoryTeller 问题是关于未捕获的异常和堆栈展开。在这种情况下,覆盖std::terminate() 是很容易出错的,不是吗?
    【解决方案2】:

    本身并不是最强有力的原因,但将其留给实现可以进行更多优化。例如:

    class Foo { /*...*/ };
    
    void f();
    
    int main()
    {
        for ( int i = 0; i < 1000000; ++i )
        {
            Foo myFoo;
            f();
        }
    }
    

    在这里,如果f() 抛出,实现可以选择不销毁myFoo,这可能会减少代码大小和/或提高性能。基本原理是,如果您不编写异常处理程序,您就不会期望f() 无论如何都会抛出,并且可能会采取捷径。这听起来可能有点弱,但这类似于 noexcept (C++11) 与 throw() 子句 (C++98) - 取消展开堆栈的要求允许更积极的优化。

    【讨论】:

    • 听起来很弱,特别是因为这个例子(代码直接在main 并且在这个TU 中都可见)是如此做作。我无法想象编译器编写者会不遗余力地对其进行优化。
    • 不一定要在main,如果用全程序优化,代码甚至可以在不同的TU,但肯定是不同的函数。并且可能无法证明f() 不会为给定的输入抛出异常。您不是偶然说小程序需要更少的优化吗? ;-)
    • 这是一个很大的“如果”!
    • 当然,很少使用链接时间优化(尽管现在大多数主要编译器都支持它)。但是我听说编译器开发人员说他们绝对会实现优化,打破格式错误的代码,即使是格式正确的代码中的微小性能提升。我参加了一个讨论,Chandler Carruth 谈到了堆栈展开的可能性有多大可能会损害优化。听起来他几乎憎恶他们。我可以看到编译器开发人员也不想基于主要理由做出这种保证——抛出的代码也应该捕获。
    • 他们当然“想要”这样做,但这与花时间做这件事实际上可行或值得这样做并不相同
    猜你喜欢
    • 1970-01-01
    • 2017-02-19
    • 1970-01-01
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 1970-01-01
    • 2011-03-20
    • 2016-07-13
    相关资源
    最近更新 更多