簡體   English   中英

異常在MSVC中的析構函數上引發異常

[英]Exception throwing an exception at destructor in MSVC

為了更好地理解它,我一直在使用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對象,直到達到堆棧溢出並崩潰為止。

如果我將析構函數更改為此片段(將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()) throw 1; 被執行。

我的問題是:這里發生了什么? 看起來x64上的MSVC不贊成這種行為,但是感覺不對,因為它確實可以在x86上工作。 我針對x86和x64使用MinGW和MinGW-w64編譯了此代碼,並且兩個程序均按預期工作。 這是MSVC中的錯誤嗎? 誰能想到一種繞過此問題的方法,或者也許是為什么微軟決定阻止在x64上發生這種情況?

謝謝。

我相信32位和64位不同的原因是32位版本使用了復制省略,而64位版本則沒有。 我可以使用-fno-elide-constructors標志在gcc重現64位版本的結果。

64位版本中發生的是任何throw Foo(); 行創建一個臨時的Foo對象,然后將其復制到存儲異常值的任何位置。 然后臨時Foo被銷毀,這又throw Foo(); 要執行的行將創建另一個被復制和銷毀的臨時文件,依此類推。 如果添加帶有打印語句的副本構造函數,則應該看到它在64位版本中反復調用,而在32位版本中根本沒有調用。


至於為什么你throw 1版本調用std::terminate ,這是因為如果一個異常在析構函數拋出而另一個例外仍在傳播, std::terminate被調用,因為沒有辦法一下子應付兩個例外。 因此,您首先throw Foo() main中的throw Foo() ,然后當臨時Foo被銷毀時,它將在其析構函數中拋出1 ,但是已經處理了Foo異常,因此該程序只是放棄並拋出std::terminate

現在,您可能想知道為什么在使用throw Foo();時為什么不發生這種情況throw Foo(); 因為沒有復制省略,實際上不會在Foo::~Foo()引發異常。 在main中第一次調用throw Foo()會創建一個臨時Foo ,將其復制並隨后調用其析構函數(尚未拋出該副本)。 在該析構函數中,創建,復制並銷毀另一個臨時Foo對象,這將創建另一個臨時Foo ...,依此類推。 因此,程序會一直進行復制,直到崩潰為止,再也不會真正達到引發這些Foo異常的地步。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM