Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is stack unwinding guaranteed only for handled exceptions?

Tags:

c++

exception

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.

like image 802
Nevermore Avatar asked Jul 03 '17 13:07

Nevermore


2 Answers

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.

like image 187
StoryTeller - Unslander Monica Avatar answered Nov 15 '22 19:11

StoryTeller - Unslander Monica


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

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.

like image 39
Arne Vogel Avatar answered Nov 15 '22 17:11

Arne Vogel