【问题标题】:Exception throwing an exception at destructor in MSVC在 MSVC 中的析构函数处引发异常的异常
【发布时间】:2013-10-30 10:59:13
【问题描述】:

我一直在使用 C++ 中的异常处理和析构函数异常来更好地理解它,但我遇到了一些我无法解释的奇怪行为(我希望这里有人可以帮助我)。

我已经编写了这个简单的代码。 这是一个异常类(Foo),在被破坏时会抛出自己。 这里的目的是让异常从抛出的任何地方传播到 main(),在那里我明确地捕获它并阻止它重新抛出自身。

#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;
}

我知道这永远不应该在 C++ 中完成,但这不是重点 - 我试图了解 MSVC 中 x86 和 x64 异常处理之间的区别(我将在下一段中解释)。

当我使用 MSVC 为 x86 编译此代码时(我主要使用 2010,但我也在 2005 和 2012 年检查过),一切都很好,并且按预期工作:

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

当我使用 MSVC 为 x64 编译此代码时,它严重失败:

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
...

此时,它会不断创建和销毁 Foo 对象,直到达到堆栈溢出并崩溃。

如果我将析构函数更改为这个 sn-p(抛出一个 int 而不是 Foo):

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

    if (keepThrowing_)
    {
        throw 1;
    }
}

我收到以下输出:

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

然后程序在执行throw 1; 时到达调试断言(调用std::terminate())。

我的问题是:这里发生了什么? 看起来 x64 上的 MSVC 不批准这种行为,但感觉不对,因为它确实适用于 x86。我使用 MinGW 和 MinGW-w64 为 x86 和 x64 编译了这段代码,两个程序都按预期工作。这是 MSVC 中的错误吗? 谁能想到绕过这个问题的方法,或者为什么微软决定阻止这种情况在 x64 上发生?

谢谢。

【问题讨论】:

    标签: c++ visual-c++ exception-handling 64-bit destructor


    【解决方案1】:

    我认为 32 位和 64 位不同的原因是 32 位版本使用 copy elision 而 64 位版本没有。我可以使用-fno-elide-constructors 标志在gcc 中重现64 位版本的结果。

    在 64 位版本中发生的情况是,任何 throw Foo(); 行都会创建一个临时的 Foo 对象,然后将其复制到存储异常值的任何位置。然后临时的Foo 被销毁,这会导致另一个throw Foo(); 行被执行,这会创建另一个被复制和销毁的临时文件,依此类推。如果添加带有 print 语句的复制构造函数,您应该会看到它在 64 位版本中被重复调用,而在 32 位版本中根本没有被调用。


    至于为什么你的throw 1版本调用std::terminate,是因为如果在析构函数中抛出异常,而另一个异常仍在传播,std::terminate会被调用,因为没有办法同时处理两个异常.所以你首先在main中throw Foo(),然后当临时Foo被销毁时,它在其析构函数中抛出1,但一个Foo异常已经被处理,所以程序只是放弃并抛出std::terminate

    现在您可能想知道为什么在使用 throw Foo(); 时不会发生这种情况。这是因为如果没有复制省略,Foo::~Foo() 中实际上永远不会引发异常。您在 main 中的第一个 throw Foo() 调用会创建一个临时的 Foo ,它会被复制,然后调用其析构函数(该副本尚未被抛出)。在该析构函数中,另一个临时 Foo 对象被创建、复制,然后被销毁,从而创建另一个临时 Foo... 等等。所以程序一直在复制,直到它崩溃,并且永远不会真正达到它抛出那些Foo异常的地步。

    【讨论】:

      猜你喜欢
      • 2014-05-26
      • 1970-01-01
      • 1970-01-01
      • 2010-11-14
      • 2017-08-16
      • 2014-01-21
      • 2012-04-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多