简体   繁体   中英

Why is stack unwinding guaranteed only for handled exceptions?

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

If no matching handler is found, the function std::terminate() is called; whether or not the stack is unwound before this call to std::terminate() is implementation-defined

For example, behavior of code below (will it print S::~S() or not) is implementation defined:

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");
}

I would like to know in-depth : why is it implementation defined? Why a context cannot be unwinded up to its entry before std::terminate() is called if an exception is uncaught (which is similar to try{ ... } catch(...) { throw; } in the top-level function)? At a glance, such behavior is much clearer and safer in consistence with RAII.

If an exception isn't caught, std::terminate is called. We failed so bad the host environment needs to step in and (maybe) clean up after us. Unwinding the stack in this case is like giving a helmet to a kamikaze pilot.

So for a hosted environment, it may make more sense to just do nothing and let the host clean up.

Now, if you are in a stand alone implementation, and are throwing exceptions, then there is no one to clean up after you. An implementation should preform stack unwinding in this case, because that's what is supposed to clean up the mess.

The standard leaves it to the implementation to facilitate these two very different execution environments.


Like @Matteo pointed out, std::terminate is called before any potential unwinding because you can setup a handler for it. And that handler can do something useful with the stack state, so long as the stack isn't unwound yet.

Not the strongest reason per se, but leaving this up to the implementation allows for more optimizations. Eg:

class Foo { /*...*/ };

void f();

int main()
{
    for ( int i = 0; i < 1000000; ++i )
    {
        Foo myFoo;
        f();
    }
}

Here, an implementation may choose not to destroy myFoo if f() throws, which may reduce code size and/or increase performance. The rationale would be if you don't write an exception handler, you don't expect f() to throw anyway, and shortcuts may be taken. This may sound a bit weak, but this is similar to noexcept (C++11) vs. throw() clause (C++98) – removing the requirement to unwind the stack allows for more aggressive optimization.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM