[C++] SEHException : 구조적 예외 처리 Strucutred Exception Handling
Structured Exception Handling (SEH) 구조적 예외 처리
https://docs.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-170
C 언어에 대한 Microsoft 확장 표현으로 하드웨어 오류 같은 예외적인 상황을 잘 처리하기 위한 예외 처리 방법이다.
윈도우와 MicroSof에서 SEH를 지원한다 하더라도 ISO-표준 C++ 예외 핸들링 기법을 사용하는 것을 권장한다.
왜냐하면 표준 예외처리가 더 유연한 코드를 만들기 떄문이다.
문법
try-except 문법
__try
{
// compound-statement
}
__except (expression)
{
// compound-statement
}
try-finally 문법
__try
{
// compound-statement
}
__finally
{
// compound-statement
}
SEH를 사용하면, C에서 발생하는 예외를 잡을 수 있으므로 더 섬세한 예외처리가 가능하다. try-except나 try-finally 구문은 C언어의 MS 확장 표현이기도 하다. SEH가 C++ 환경에서 동작하기는 하지만 완전히 C++를 위하여 디자인되지는 않았다. 따라서 컴파일 옵션으로 /EHa나 /EHsc를 주어야 사용 가능하다.
예외 핸들러, __except 블록 : 예외를 없애는것 혹은 반응에 대한 내용
종료 핸들러, __finally 블록 : 예외가 종료를 불러일으키든지 아닌지 항상 호출된다.
두 종류의 핸들러는 distinct하지만 스택 unwinding 절차와 깊은 연관이 있다. 구조적 예외가 던져지면, 윈도우는 가장 최근에 설치된 예외 핸들러부터 찾아 나간다.
예외 핸들러(exception handler)는 예외를 던진 위치보다 더 상위 스택에 있을 수 있다. 그래서 예외를 던진 함수는 스택 프레임을 정리하며 되감기(unwound)된다. 정적이 아닌 로컬 변수들이 스택에서 클리어 된다.
종료 핸들러(termination handler)는 주로 예외를 던진 이후 해제시켜야할 리소스들을 없애는 것을 목적으로 한다. 그렇지 않으면 비정상적인 종료로 정리되지 않은 리소스들이 남아있을 것이기 때문이다. 예를 들면 락을 획득한 상태에서 예외를 던진경우 종료핸들러를 이용하여 락을 다시 반납할 수 있다.
다음 MS 예시는 널 포인터 접근에 대한 구조적 예외 처리중 예외 핸들러 사용에 대한 내용이다.
#include <stdio.h>
#include <Windows.h>
#include <exception>
class TestClass
{
public:
~TestClass()
{
printf("Destroying TestClass!\r\n");
}
};
__declspec(noinline) void TestCPPEX()
{
#ifdef CPPEX
printf("Throwing C++ exception\r\n");
throw std::exception("");
#else
printf("Triggering SEH exception\r\n");
volatile int* pInt = 0x00000000;
*pInt = 20;
#endif
}
__declspec(noinline) void TestExceptions()
{
TestClass d;
TestCPPEX();
}
int main()
{
__try
{
TestExceptions();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("Executing SEH __except block\r\n");
}
return 0;
}
C++에서 구조적 예외 핸들하기
C에 대한 구조적 예외 처리와 C++ 예외 처리의 차이점은 C++에서는 타입 std::exception에 대한 예외 처리를 하는 방면, 구조적 예외처리는 unsigned int에 대해서만 예외 처리를 하게 되어 있다. 양의 정수 (unsigned int)로 확인되는 C 예외는 C++ 데이터 타입으로 해석되어 질 수 있다. C 구조적 예외가 던져질 때, 항상 어디선가 핸들러가 있다면 처리가 가능하나 C++에서는 어느 타입으로나 던져질 수 있기 때문에 예외가 있다.
C 구조적 예외처리의 경우 비동기로 처리되어진다는 점이 다르다. C 구조적 예외처리의 경우 일반 적인 제외흐름에서 부수적인 발생으로 생성되나 C++에서는 항상 동기적으로 예외가 던져진다.
일반적으로 C 구조적 예외처리를 핸들링하려면 C++ 예외 처리 구문 안에 추가해야한다.
// exceptions_Exception_Handling_Differences.cpp
// compile with: /EHa
#include <iostream>
using namespace std;
void SEHFunc( void );
int main() {
try {
SEHFunc();
}
catch( ... ) {
cout << "Caught a C exception."<< endl;
}
}
void SEHFunc() {
__try {
int x, y = 0;
x = 5 / y;
}
__finally {
cout << "In finally." << endl;
}
}
C 예외 처리기를 래퍼하여 예외 클래스를 만듦
https://docs.microsoft.com/en-us/cpp/cpp/exception-handling-differences?view=msvc-170
C 예외는 항상 unsigned int 양의 정수를 받으므로 다음과 같은 래퍼 클래스를 만들 수 있다.
// exceptions_Exception_Handling_Differences2.cpp
// compile with: /c
class SE_Exception {
private:
SE_Exception() {}
SE_Exception( SE_Exception& ) {}
unsigned int nSE;
public:
SE_Exception( unsigned int n ) : nSE( n ) {}
~SE_Exception() {}
unsigned int getSeNumber() {
return nSE;
}
};
이 예외 타입을 사용하려면, 내부적으로 C 예외를 전달해주는 함수를 설치해야한다. translation 함수에 C 예외에 대한 unsigned int 값을 가직 SE_Exception을 생성하여 C++ catch 구문에서 잡도록 던지기만 하면 된다.
// exceptions_Exception_Handling_Differences3.cpp
// compile with: /EHa
#include <stdio.h>
#include <eh.h>
#include <windows.h>
class SE_Exception {
private:
SE_Exception() {}
unsigned int nSE;
public:
SE_Exception( SE_Exception& e) : nSE(e.nSE) {}
SE_Exception(unsigned int n) : nSE(n) {}
~SE_Exception() {}
unsigned int getSeNumber() { return nSE; }
};
void SEFunc() {
__try {
int x, y = 0;
x = 5 / y;
}
__finally {
printf_s( "In finally\n" );
}
}
void trans_func( unsigned int u, _EXCEPTION_POINTERS* pExp ) {
printf_s( "In trans_func.\n" );
throw SE_Exception( u );
}
int main() {
_set_se_translator( trans_func );
try {
SEFunc();
}
catch( SE_Exception e ) {
printf_s( "Caught a __try exception with SE_Exception.\n" );
printf_s( "nSE = 0x%x\n", e.getSeNumber() );
}
}
_set_se_translator의 경우 trans_func 함수를 설치하는 Windows 함수이다.
따라서 SEFunc()를 호출할 때 Zero Division에 따른 C 예외가 던져지고 trans_func이 호출된다음 SE_Exception이 던져지는 구조이다.
In trans_func.
In finally
Caught a __try exception with SE_Exception.
nSE = 0xc0000094