2013-10-21 4 views
1

C++에서 예외 처리 및 소멸자 예외를 사용하여 더 잘 이해하고 있으며 설명 할 수없는 이상한 동작이 발생했습니다. 나를 도와 줄 수 있니).MSVC의 소멸자에서 예외를 throw하는 예외

이 간단한 코드를 작성했습니다. 이것은 예외 클래스 (Foo)로, 파기 될 때 자신을 버립니다. 여기서 의도는 main()으로 던져진 곳에서 예외를 전파하는 것입니다. main()에서는 예외를 명시 적으로 catch하고 재실행을 중단합니다.

#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를 사용하여 86이 코드를 컴파일

는 (나는 주로 2010을 사용하지만, 나는 또한 2005 년과 2012 년이 체크), 모든 것이 괜찮 그것은 예상대로 작동합니다

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

내가 컴파일 할 때 MSVC를 사용하여 64이 코드는, 그것은 심하게 실패

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() 
{ 
    std::cout << "Foo destroyed: " << this << std::endl; 

    if (keepThrowing_) 
    { 
     throw 1; 
    } 
} 

:

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

그리고 프로그램이 디버그 주장에 도달 (나는이 조각에 소멸자를 변경하는 경우

(대신 푸의 int를 던지는) std :: terminate()가 호출되면 throw 1;이 실행됩니다.

내 질문은 : 여기 어떻게됩니까? x64에서 MSVC가이 동작을 승인하지 않았지만 x86에서 작동하기 때문에 올바르게 작동하지 않습니다. MinGW와 MinGW-w64를 사용하여이 코드를 컴파일했고 x86과 x64 모두에 대해 두 프로그램 모두 예상대로 작동했습니다. 이것은 MSVC의 버그입니까? 누구나이 문제를 우회하는 방법을 생각해 볼 수 있습니까? 아니면 Microsoft가 x64에서이 문제를 방지하기로 결정한 이유는 무엇입니까?

감사합니다.

답변

2

32 비트와 64 비트가 다른 이유는 32 비트 버전이 copy elision이고 64 비트 버전은 그렇지 않다는 것입니다. -fno-elide-constructors 플래그를 사용하여 gcc에 64 비트 버전의 결과를 재현 할 수 있습니다.

throw Foo(); 행은 임시 값 Foo 개체를 생성 한 다음 예외 값이 저장되는 곳으로 복사됩니다. 그런 다음 임시 Foo이 삭제되어 다른 throw Foo(); 행이 실행되어 복사되고 파기되는 다른 임시 파일을 만듭니다. print 문에 복사 생성자를 추가하면 64 비트 버전에서 반복적으로 호출되고 32 비트 버전에서는 전혀 호출되지 않는 것을 볼 수 있습니다.다른 예외가 여전히 전파하는 동안 예외가 소멸자에 던져 경우 한 번에 두 가지 예외를 처리 할 수있는 방법이 없기 때문에, std::terminate가 호출되는 때문에 throw 1 버전 std::terminate를 호출하는 이유에 관해서는


, 그것은이다. 그래서 먼저 throw Foo()을 메인에 넣은 다음, 임시 Foo이 파괴되면, 소멸자에 1을 던지지만, Foo 예외가 이미 처리 중이므로 프로그램이 그냥 포기하고 std::terminate을 던집니다.

이제 throw Foo();을 사용할 때 이것이 발생하지 않는 이유가 궁금 할 수 있습니다. 그 이유는 copy elision이 없기 때문에 실제로는 Foo::~Foo()에 예외가 던져지지 않기 때문입니다. 처음으로 throw Foo()을 호출하면 임시 Foo이 생성되고 복사 된 다음 소멸자가 호출됩니다 (아직 복사되지 않았습니다). 그 소멸자에서 또 다른 임시 Foo 객체가 생성되고, 복사되고, 파괴되어 다른 임시 Foo ...을 생성합니다. 따라서 프로그램은 충돌이 발생할 때까지 계속 복사본을 만들고 실제로 Foo 예외를 throw하는 지점에 도달하지 않습니다.

관련 문제