본문 바로가기
Visual C++/General

__try ... __except .. __finally 사용하기 간단 강좌.

by hyperhand 2009. 6. 5.
이 강좌는 이미 우리 회사에서는 많이 사용하고 있기때문에 쓸 필요를 느끼지는 못했지만 , 혹시나하는 마음에 간단하게 설명을 해보겠다.
 
예외 처리에는 크게 두 부분으로 생각할수있다. 
1) 전역 예외 처리
2) 지역 예외 처리
 

 

 
1) 전역 예외 처리
    char *p=NULL;
    *p = 10;
 
위의 문장이 있을때 p 변수의 메모리가 크기가 할당되지 않았기 때문에 오류를 일으키면(access violation) 프로그램이 죽어버린다.
이런식으로 할당되지 않은 메모리를 참조해서 쓰거나 읽어들이는 문장은 프로그램 어디에서든 발생될수있기때문에 항상 주의를 해야 하지만 , 인간이라는 동물이 매사 조심성이 있지않아 꼭 실수를 하게 마련이다.
 
 
 
 
이때 프로그램 어디서든 생길수있는 이러한 오류를 감지하려고 만든것이 전역 예외 처리자 이다.
 
LONG CALLBACK exception_filter(PEXCEPTION_POINTERS pExceptionInfo)
{
    printf( "ERR_CODE = 0x%x , ERR_EIP = 0x%x \n"
        ,pExceptionInfo->ExceptionRecord->ExceptionCode
        ,(int)pExceptionInfo->ExceptionRecord->ExceptionAddress);
 
return EXCEPTION_EXECUTE_HANDLER;
}
 
// 전역 예외처리자를 셋팅
void SetGlobalException()
{
    SetUnhandledExceptionFilter(exception_filter);
}
 
이렇게 SetUnhandledExceptionFilter()함수를 이용해서 예외처리 필터를 셋팅하고 exception_filter()에서는 여러가지 정보중에 제일 기본이 되는 EIP와 오류 코드를 출력하게 하였다.
 
이제 SetGlobalException() 함수를 호출하고 위의 오류 문장을 호출해보면 오류가 발생된 지역이 어디인지 알수있게된다. (확인은 첨부한 소스를 돌려 보시길...)
 
하지만 위의 소스로는 프로그램이 오류가 생겼을때 감지하는 역할뿐 , 그 다음 프로그램이 계속 동작 시켜야할지를 판단하는 구문이 없다.
 
이때 지역 예외 처리를 하게 된다.

 

 

 

 
2) 지역 예외 처리자
__try ... __except .. __finally 와 try .. catch로 대변되는 지역 예외 처리자는 오류의 발생과 그 처리에 대한 설정을 할수있다. (여기서는 __try ... __except .. __finally 에 대한 설명만 하겠다.)
 
기본적인 처리 구문은 다음과 같다.
 
    __try
    {
        printf( "in __try. \n");
 
        char *p=NULL;
        *p = 10;
    }
    __except( EXCEPTION_EXECUTE_HANDLER )
    {
        printf( "ERROR Found. \n");
    }
 
    printf( "END. \n");
 
    /* 결과
    Case 1.
    in __try.
    ERROR Found.
    END.
    */
 
오류가 발생되면 __except 구문으로 들어가라는 것이다.
 
이때 __except에는 EXCEPTION_EXECUTE_HANDLER,EXCEPTION_CONTINUE_EXECUTION,EXCEPTION_CONTINUE_SEARCH 세가지 값과 처리 함수가 올수있는데 세가지 상수에 대한 의미는 다음과 같다.
 
EXCEPTION_EXECUTE_HANDLER      : 오류가 생겨도 계속 진행 , 다음 구문을 계속 실행할수 있다.
EXCEPTION_CONTINUE_EXECUTION : 오류가 발생한 구문을 계속 실행
EXCEPTION_CONTINUE_SEARCH      : 오류가 생기면 정지 , 전역 예외 핸들러가 있으면 그넘이 작동
 
이정도면 기본적인 사항은 모두 이해 했을것이다. 단순하게 생각에서 __try 코드를 트라이해보고 문제가 생기면 예외 처리 구문으로 보내라는 것이다. 
 
 
 
 
함수를 셋팅해서 EIP 등을 뽑아 보자.
 
DWORD GetLocalException(PEXCEPTION_POINTERS pExceptionInfo)
{
    printf( "ERR_CODE = 0x%x , ERR_EIP = 0x%x \n"
        ,pExceptionInfo->ExceptionRecord->ExceptionCode
        ,(int)pExceptionInfo->ExceptionRecord->ExceptionAddress);
 
return EXCEPTION_EXECUTE_HANDLER;
}
 
    printf("\nCase 3. \n");
 
    __try
    {
        printf( "in __try. \n");
        
        char *p=NULL;
        *p = 10;
    }
    __except( GetLocalException(GetExceptionInformation()) )
    {
        printf( "ERROR Found. \n");
    }
 
    printf( "END. \n");
 
/* 결과
 
    Case 3.
    in __try.
    ERR_CODE = 0xc0000005 , ERR_EIP = 0x404941
    ERROR Found.
    END.
 
    */
 
이렇게 하면 오류가 발생했을때 __except 안으로 들어가기전에 지정한 함수를 먼저 처리하게 된다. 이때 위의 전역 처리자처럼 EIP등을 표시하게 하였다.
 
만약 전역 예외 처리자와 지역 예외 처리자를 동시에 셋팅하게 되면 , 지역 처리자가 우선하게 작동하게 된다. 
 
 
 
 
__finally 문은 성격이 조금 다르다. 아래의 소스를 보자.
 
printf("\nCase 4. \n");
 
    __try
    {
        printf( "in __try. \n");
        
        return ;                    // <-- 이것을 해도 __finally 로 진입
    }
    __finally
    {
        printf( "in finally. \n");
    }
 
    printf( "END. \n");
 
    /* 결과
 
    Case 4.
    in __try.
    in finally.
    */
 
return 문이 있는데 리턴을 시키는것이 아니고 , finally 구문으로 들어왔다.
 
finally 는 이렇게 try문안에 리턴이나 , 오류가 발생했을때 마지막에 꼭 여기를 들어오게 처리를 한다.
 
아래 예제를 보자.
 
printf("\nCase 5. \n");
 
    __try
    {
        printf( "in __try [finally]. \n");
 
        __try
        {
            printf( "in __try [except]. \n");
        
            char *p=NULL;
            *p = 10;
        }
        __except( GetLocalException(GetExceptionInformation()) )
        {
            printf( "ERROR Found. \n");
        }
 
        printf( " GO \n");
 
    }
    __finally
    {
        printf( "in finally. \n");
    }
 
    printf( "END. \n");
 
    /* 결과
 
    Case 5.
    in __try [finally].
    in __try [except].
    ERR_CODE = 0xc0000005 , ERR_EIP = 0x407315
    ERROR Found.
     GO                                 <-- ** 이부분 호출이된다. 
    in finally.
    END.
 
    */
 
오류가 발생했을때 __except에서 오류 처리를 하고 마지막에는 finally까지 도달한것을 알수있다.
 
이러한 이유로 __try ... __finally 문장은 메모리의 Lock 과 UnLock 부분에 많이 이용 된다. Lock 을 하고선 뒤에 문장에 return 이 많을때 실수로 UnLock을 하지않으면 문제가 발생되기 때문에 아래와 같이 많이 설정하고는 한다. 
 
__try
{
    Lock();
 
    if (A == 1)
        return 10;
    .....
    
    if (!B )
        return 11;
}
__finally
{
    UnLock();
}
 
위와 같은 경우 return 10; 과 return 11; 전에 UnLock()을 해줘야 되는데 , __finally 덕분에 수고를 많이 덜어줄수도 있고 , 오류를 예방하는데도 큰 도움이 된다.
 
 
 
 
또한 만약 문장중에 오류가 발생한다면 Case 5 와 같이 구성을 해서 __finally로 무조건 오게해서 UnLock()을 시켜준다면 오류가 생겼을때 UnLock)을 하지않아서 또 다른 문제가 생기는것을 막아줄수도 있을것이다. 
 
 
그럼 __except와 __finally 그 순서를 바꾸면 어떻게 될까 ?
 
printf("\nCase 6. \n");
 
    __try
    {
        printf( "in __try [except]. \n");
    
        __try
        {
            printf( "in __try [finally]. \n");
 
            char *p=NULL;
            *p = 10;
        }
        __finally
        {
            printf( "in finally. \n");
        }
 
        printf( " GO \n");
    }
    __except( GetLocalException(GetExceptionInformation()) )
    {
        printf( "ERROR Found. \n");
    }
 
    printf( "END. \n");
 
    /* 결과
 
    Case 6.
    in __try [except].
    in __try [finally].
    ERR_CODE = 0xc0000005 , ERR_EIP = 0x4067c5      <-- 순서 주목
    in finally.                                     <-- (1) __finally 먼저 들어간다
    ERROR Found.                             <-- (2) __except 구문이 뒤로 온다.
    END.                                          <-- GO 호출되지 않고 끝난다.
 
    */
 
아주 재미있는 결과가 나온다.
 
먼저 에러를 감지하고 , 어쨌든 finally가 들어오고 GO 부분이 호출이 되지않았다. 
오류가 생기면 GO부분으로 진행하지않고 finally에서 끝을 내라는 C 언어 개발자들의 의지를 읽을수있다.
 
또 재미있는점은 __finally 안에 문장이 __except 안에 문장보다 먼저 수행된다는 점이다. 이점에 유의해서 코드를 설계해주는것이 로직을 구현하는데 주의할 점이다.  
 
 
 
 
마지막으로 __try .. __except 구문에서 new를 통한 생성자 호출을 할수있는 팁을 소개한다. (다들 알고 있을려나 ??)
 
typedef struct tagST
{
    int a;
    int b;
 
    tagST() { a=0; b=0; };
}ST;
 
void try_main7()
{
    printf("\nCase 1. \n");
 
    ST * pBuf=NULL;
 
    __try
    {
        printf( "in __try. \n");
 
        pBuf = new ST;
 
        char *p=NULL;
        *p = 10;
    }
    __except( EXCEPTION_EXECUTE_HANDLER )
    {
        printf( "ERROR Found. \n");
    }
 
    printf( "END. \n");
}
 
이렇게 하면 error C2712: Cannot use __try in functions that require object unwinding 오류가 발생한다.
 
그럼 이렇게 하면 된다.
 
ST * newST()
{
    return new ST;
}
 
이렇게 함수를 만들고 아래와 같이 호출하면 된다.
 
        pBuf = newST();
 
ㅋㅋ 너무 간단한가 ??
반응형