C언어를 사용하다 보면 예외처리 부분이 상당히 미흡하다는 것을 알 수 있습니다. C++은 언어 차원에서 try, catch라는 예외처리 문법을 제공해 주고 있습니다.

C언어는 C 표준 라이브러리에 있는 setjmp(), longjmp() 함수를 사용하여 예외처리를 할 수 있지만 상당히 구식이라 직관적이지도 못하고 사용하기도 불편합니다.

그래서 C언어에서는 Structured Exception Handler(SEH)라는 예외처리 방식을 사용할 수 있습니다. SEH는 Microsoft Visual C++ 컴파일러 차원에서 제공하는 예외처리 문법입니다. 물론 윈도우상에서만 사용할 수 있습니다.

void tryexcept()
{
    __try
    {
        int *world = NULL;

        *world = 8;  // 널 포인터에 값 대입

        printf("hello try %d\n", *world);
    }  
    __except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
        EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
    {
        printf("EXCEPTION_ACCESS_VIOLATION\n");
    }
}

__try 부분에 예외를 검사할 코드를 넣습니다. 예외가 발생하면 __except 부분에서 지정한 코드가 실행되게 됩니다. 예외도 종류가 엄청 많은데, __except의 괄호 안에 예외 검사식을 넣어줘야 합니다.

위 예제의 경우 널 포인터에 값을 대입하고 있습니다. 이 코드를 실행하면 메모리 접근 위반 예외가 발생하고 __except 부분의 코드가 실행됩니다. 괄호안의 부분이 메모리 접근 위반 예외인지 판단하는 부분인데, GetExceptionCode() 함수로 현재 발생한 예외 코드를 구해옵니다. 그리고 그 예외가 EXCEPTION_ACCESS_VIOLATION (메모리 접근 위반 예외)인지 검사하고 맞으면 __except 블록 안의 코드를 실행하고 아니면, 상위 예외 처리 핸들러에게 넘깁니다.

  • EXCEPTION_EXECUTE_HANDLER (1)는 __except 블록 안의 코드를 실행합니다.
  • EXCEPTION_CONTINUE_SEARCH (0)는 __except 블록 안의 코드를 실행하지 않고 상위 예외 처리 핸들러에게 넘깁니다.
  • EXCEPTION_CONTINUE_EXECUTION (-1)은 예외를 무시하고 예외가 발생한 부분 부터 코드를 다시 실행합니다. 하지만 예외가 발생한 원인을 해결해 주지 못하면 무한루프에 빠집니다.

이 매크로들은 excpt.h에 정의되어 있습니다.

__except의 괄호 안에 모든 예외의 종류를 다 적어줄 순 없는 노릇입니다. 그래서 예외 코드를 판별하여 각각 처리해주는 함수를 만들어 사용할 수 있습니다. 여기서 예외의 원인을 수정하여 EXCEPTION_CONTINUE_EXECUTION를 리턴하면 코드를 계속 실행 할 수 있습니다.

int Value = 0;

DWORD ExceptionFilter(DWORD ExceptionCode)
{
    switch (ExceptionCode)
    {
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
        {
            // 예외가 발생한 원인을 수정
            Value = 1;

            return EXCEPTION_CONTINUE_EXECUTION;
        }
    case EXCEPTION_ACCESS_VIOLATION:
        {
            // 예외가 발생한 원인을 수정

            return EXCEPTION_CONTINUE_EXECUTION;
        }
    // 다른 여러가지 예외 코드를 처리
    //case EXCEPTION_XXX
    default:
        return EXCEPTION_EXECUTE_HANDLER;
    }
}

void tryexceptfilter()
{
    __try
    {
        int world = 10;

        world = world / Value;
    }  
    __except (ExceptionFilter(GetExceptionCode()))
    {
        printf("EXCEPTION !\n");
    }
}

위 예제는 GetExceptionCode() 함수를 이용하여 예제코드를 ExceptionFilter() 함수에 넘기고 각 코드에 맞는 예외의 원인을 수정하도록 되어 있습니다.

world를 Value로 나누었는데 Value는 0입니다. 그래서 EXCEPTION_INT_DIVIDE_BY_ZERO예외가 발생하였고, ExceptionFilter() 함수에서는 이 예외 코드에 맞게 Value에 1을 대입하여 주었습니다. 그리고 EXCEPTION_CONTINUE_EXECUTION를 리턴하여 예외가 발생한 부분에서 코드를 계속 실행하게 됩니다.

이 ExceptionFilter()에 여러가지 다른 예외 코드를 추가해서 처리할 수 있습니다. 물론 여기에 추가되지 않은 예외가 발생하면 EXCEPTION_EXECUTE_HANDLER가 리턴되고 __except의 부분이 실행됩니다.

이번에는 __finally의 사용입니다. __finally 부분은 __try 부분이 실행에 성공하던 예외가 발생하던 무조건 실행됩니다.

void tryfinally()
{
    __try
    {
        printf("hello try\n");
    }  
    __finally
    {
        printf("hello finally\n");
    }
}

주로 사용되는 패턴은 __try 부분에서 동적 메모리 등을 할당했을때 __finally 부분에서 메모리가 할당 되었으면 해제하는 식입니다. __finally는 무조건 실행되기 때문에 메모리를 할당 했더라도 어떤 예외가 발생하여 프로그램이 중단되면 __finally 부분에서 할당한 메모리를 해제 할 수 있습니다.

비슷한 패턴으로는 크리티컬 섹션이나, 뮤텍스등 동기화 객체를 사용할 때, 객체를 획득한 상태에서 예외가 발생했더라도 __finally 부분에서 획득한 동기화 객체를 해제 할 수도 있습니다.

__try 안에서 return을 하더라도 __finally 부분이 실행되고 값을 리턴 합니다.

이 __finally는 __except와는 동시에 사용할 수 없습니다. 무조건 __try, __finally 혹은 __try, __except로만 사용할 수 있습니다.


저작권 안내

이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.
  • 블로그, 게시판 등에 퍼가는 것을 금지합니다.
  • 비공개 포스트에 퍼가는 것을 금지합니다.
  • 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
  • 링크 및 SNS 공유는 허용합니다.

Published

25 August 2007