简体   繁体   中英

Exception throwing an exception at destructor in MSVC

I've been playing with exception handling and destructor exceptions in C++ to understand it better, and I've encountered some weird behavior I can't explain (and I'm hoping someone here can help me).

I've written this simple code. This is an exception class (Foo), that throws itself when destructed. The intention here is to have an exception propagate from wherever it is thrown up to main(), where I catch it explicitly and stop it from rethrowing itself.

#include <iostream>

class Foo
{
public:
    Foo();

    virtual ~Foo();

    void stopThrowing() { keepThrowing_ = false; }

private:
    bool keepThrowing_;
};

Foo::Foo(): keepThrowing_(true)
{
    std::cout << "Foo created: " << this << std::endl;
}

Foo::~Foo()
{
    std::cout << "Foo destroyed: " << this << std::endl;

    if (keepThrowing_)
    {
        throw Foo();
    }
}

int main()
{
    try {
        try {
            throw Foo();
        } catch (const Foo&) {
            std::cout << "Foo caught" << std::endl;
        }
    } catch (Foo& ex) {
        std::cout << "Foo caught 2" << std::endl;
        ex.stopThrowing();
    } catch (...) {
        std::cout << "Unknown exception caught 2" << std::endl;
    }

    std::cout << "Done" << std::endl;

    return 0;
}

I know this should never be done in C++, but that is not the point - what I'm trying to understand what is different between x86 and x64 exception handling in MSVC (as I'll explain in the next paragraph).

When I compile this code for x86 using MSVC (I used 2010 mainly, but I also checked this in 2005 and 2012), everything is fine, and it works as expected:

Foo created: 001AFC1C
Foo caught
Foo destroyed: 001AFC1C
Foo created: 001AF31C
Foo caught 2
Foo destroyed: 001AF31C
Done

When I compile this code for x64 using MSVC, it fails badly:

Foo created: 000000000047F9B8
Foo caught
Foo destroyed: 000000000047F9B8
Foo created: 000000000047D310
Foo destroyed: 000000000047D310
Foo created: 000000000047C150
Foo destroyed: 000000000047C150
Foo created: 000000000047AF90
Foo destroyed: 000000000047AF90
Foo created: 0000000000479DD0
...

At which point, it keeps creating and destroying Foo objects until it reaches a stack overflow, and crashes.

If I change the destructor to this snippet (throwing an int instead of Foo):

Foo::~Foo()
{
    std::cout << "Foo destroyed: " << this << std::endl;

    if (keepThrowing_)
    {
        throw 1;
    }
}

I receive the following output:

Foo created: 00000000008EF858
Foo caught
Foo destroyed: 00000000008EF858

And then the program reaches a debug assertion (std::terminate() is called), when throw 1; is executed.

My question is: what is happening here? It looks like MSVC on x64 does not approve this behavior, but it just does not feel right, since it does work on x86. I compiled this code using MinGW and MinGW-w64, for both x86 and x64, and both programs worked as expected. Is this a bug in MSVC? Can anyone think of a way to bypass this problem, or maybe why Microsoft decided to prevent this from happening on x64?

Thanks.

I believe the reason the 32 bit and 64 bit are different is that the 32 bit version is using copy elision while the 64 bit version is not. I can reproduce the result of the 64 bit version in gcc by using the -fno-elide-constructors flag.

What is happening in the 64 bit version is any throw Foo(); line creates a temporary Foo object which is then copied to wherever the exception value is stored. The temporary Foo is then destroyed, which causes another throw Foo(); line to be executed which creates another temporary that is copied and destroyed, and so on. If you add a copy constructor with a print statement you should see it repeatedly called in the 64 bit version and not called at all in the 32 bit version.


As for why your throw 1 version calls std::terminate , it's because if an exception is thrown in a destructor while another exception is still being propagated, std::terminate gets called since there's no way to deal with two exceptions at once. So you first throw Foo() in main, then when the temporary Foo gets destroyed, it throws 1 in its destructor, but a Foo exception is already being handled so the program just gives up and throws std::terminate .

Now you may be wondering why this doesn't happen when you use throw Foo(); . Its because without the copy elision an exception is never actually thrown in Foo::~Foo() . Your first throw Foo() call in main creates a temporary Foo which is copied and then its destructor is called (the copy hasn't yet been thrown). In that destructor another temporary Foo object is created, copied, and then destroyed, which creates another temporary Foo ... and so on. So the program keeps making copies until it crashes and never actually gets to the point where it throws those Foo exceptions.

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