1

私はそれをよりよく理解するために 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 オブジェクトの作成と破棄を続けます。

デストラクタをこのスニペットに変更すると (Foo の代わりに int をスロー):

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

    if (keepThrowing_)
    {
        throw 1;
    }
}

次の出力が表示されます。

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

そして、プログラムが実行されると、デバッグ アサーションに到達します (std::terminate() が呼び出されます) throw 1;

私の質問は、ここで何が起こっているのですか? x64 上の MSVC はこの動作を承認していないように見えますが、x86 では動作するため、正しくないと感じます。x86 と x64 の両方で、MinGW と MinGW-w64 を使用してこのコードをコンパイルしたところ、両方のプログラムが期待どおりに動作しました。これは MSVC のバグですか? 誰かがこの問題を回避する方法を考えられますか、それとも Microsoft が x64 でこれが起こらないようにすることを決めた理由はありますか?

ありがとう。

4

1 に答える 1

2

32 ビットと 64 ビットが異なる理由は、32 ビット版ではコピー省略が使用されているのに対し、64 ビット版ではコピー省略が使用されていないためだと思います。フラグgccを使用することで、64 ビット版の結果を再現できます。-fno-elide-constructors

64ビットバージョンで何が起こっているかというと、どのthrow Foo();行でも一時Fooオブジェクトが作成され、例外値が保存されている場所にコピーされます。その後、テンポラリFooが破棄され、別のthrow Foo();行が実行され、コピーされて破棄される別のテンポラリが作成されます。print ステートメントを使用してコピー コンストラクターを追加すると、64 ビット バージョンでは繰り返し呼び出され、32 ビット バージョンではまったく呼び出されないことがわかります。


throw 1バージョンが を呼び出す理由についてstd::terminateは、別の例外がまだ伝播されている間にデストラクタで例外がスローされた場合、std::terminate2 つの例外を一度に処理する方法がないため、 が呼び出されるためです。したがって、最初throw Foo()にメインで、次に一時が破棄されると、デストラクタがFooスローされますが、例外はすでに処理されているため、プログラムはあきらめてスローします. 1Foostd::terminate

を使用すると、なぜこれが起こらないのか不思議に思うかもしれませんthrow Foo();。これは、コピーの省略がないと、例外が実際にスローされないためFoo::~Foo()です。main での最初のthrow Foo()呼び出しにより、コピーされる一時Fooが作成され、そのデストラクタが呼び出されます (コピーはまだスローされていません)。そのデストラクタでは、別の一時Fooオブジェクトが作成され、コピーされてから破棄されます。これにより、別の一時オブジェクトが作成されFooます...など。したがって、プログラムはクラッシュするまでコピーを作成し続け、実際にそれらのFoo例外をスローするポイントに到達することはありません。

于 2013-10-21T19:58:37.853 に答える