윈도우즈 시스템 프로그래밍이라는 책과 해당 책의 저자이신 윤성우님의 강의를 통해 공부한 내용을 정리하는 글입니다.
종료 핸들러는 무조건 실행이라는 특징을 지니지만, 예외 핸들러는 예외 상황 시 선별적 실행이라는 특징을 가지고 있다.
예외 핸들러와 필터(Exception Handler & Filters)
__try
{
// 예외 발생 경계 지역
}
__except (예외처리 방식)
{
// 예외처리를 위한 코드 지역
}
⦁ __try 블록은 예외 상황이 발생 가능한 영역을 묶는데 사용된다.
⦁ __except 블록은 __try 블록에서 발생한 예외 상황을 처리한다.
⦁ 예외처리 방식이라고 써있는 부분은 예외필터라고 한다. 이는 예외처리 매커니즘의 동작방식을 결정하는 영역이다. 이 곳에 올 수 있는 값은 필터 표현식이라고 하며, EXCEPTION_EXECUTE_HANDLER, EXCEPTION_CONTINUE_EXCUTION, EXCEPTION_CONTINUE_SEARCH 가 올 수 있다.
⦁ SEH_FlowView.cpp
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
int _tmain(int argc, TCHAR* argv[])
{
_tprintf(_T("start point! \n"));
int* p = NULL;
__try
{
*p = 100;
_tprintf(_T("value: %d \n"), *p);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
_tprintf(_T("exception occurred! \n"));
}
_tprintf(_T("end point! \n"));
return 0;
}
⦁ 실행결과
할당되지 않은 주소에 값을 쓰려고 하므로 예외 상황이 발생한다.
*p = 100 부분에서 예외가 발생하는데, 이때 __except 블록이 실행되어 예외를 처리한다. 그 뒤에 end point!를 출력하는 출력문이 실행된다. 즉, 예외 상황이 발생한 다음 문장은 실행되지 않는다는 것을 확인할 수 있다.
예외필터 EXCEPTION_EXECUTE_HANDLER를 선언할 경우 예외 상황이 발생한 라인 이후의 __try 블록의 구문은 건너뛴다.
이렇게 예외를 처리하면 프로그램이 강제 종료되지 않는다. Windows는 예외가 적절히 처리된 것으로 간주하고 예외필터가 명시한대로 실행을 한다.
예외 핸들러의 활용 사례
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
enum{DIV=1,MUL,ADD,MIN,EXIT};
DWORD ShowMenu(void);
BOOL Calculator(void);
int _tmain(int argc, TCHAR* argv[])
{
BOOL state;
do {
state = Calculator();
} while (state == TRUE);
return 0;
}
DWORD ShowMenu(void)
{
DWORD sel;
_fputts(_T("-----Menu----- \n"), stdout);
_fputts(_T("num 1: Divide \n"), stdout);
_fputts(_T("num 2: Multiple \n"), stdout);
_fputts(_T("num 3: Add \n"), stdout);
_fputts(_T("num 4 : Minus \n"), stdout);
_fputts(_T("num 5 : Exit \n"), stdout);
_fputts(_T("SELECTION >>"), stdout);
_tscanf(_T("%d"), &sel);
return sel;
}
BOOL Calculator(void)
{
DWORD sel;
int num1, num2, result;
sel = ShowMenu();
if (sel == EXIT)
return FALSE;
_fputts(_T("Input num1, num2: "), stdout);
_tscanf(_T("%d %d"), &num1, &num2);
__try
{
switch (sel)
{
case DIV:
result = num1 / num2;
_tprintf(_T("%d / %d = %d \n\n"), num1, num2, result);
break;
case MUL:
result = num1 * num2;
_tprintf(_T("%d * %d = %d \n\n"), num1, num2, result);
break;
case ADD:
result = num1 + num2;
_tprintf(_T("%d + %d = %d \n\n"), num1, num2, result);
break;
case MIN:
result = num1 - num2;
_tprintf(_T("%d - %d = %d \n\n"), num1, num2, result);
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
_tprintf(_T("Wrong number inserted. Try again! \n\n"));
}
return TRUE;
}
실행 결과
나눗셈에서 두 번째 피연산자가 0이 전달되었을 경우 예외가 발생하여 식을 출력하는 출력문이 필요없어지므로 건너뛰어야 마땅하다. 이럴때 예외필터 EXCEPTION_EXECUTE_HANDLER가 유용하게 사용될 수 있다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
enum{DIV=1, MUL, ADD, MIN, EXIT};
DWORD ShowMenu(void);
BOOL Calculator(void);
void Divide(int, int);
void Multiple(int, int);
void Add(int, int);
void Min(int, int);
int _tmain(int argc, TCHAR* argv[])
{
BOOL state;
do {
state = Calculator();
} while (state == TRUE);
return 0;
}
DWORD ShowMenu(void)
{
DWORD sel;
_fputts(_T("-----Menu----- \n"), stdout);
_fputts(_T("num1 : Divide \n"), stdout);
_fputts(_T("num2 : Multiple \n"), stdout);
_fputts(_T("num 3: Add \n"), stdout);
_fputts(_T("num 4 : Minus \n"), stdout);
_fputts(_T("num 5 : Exit \n"), stdout);
_fputts(_T("SELECTION >>"), stdout);
_tscanf(_T("%d"), &sel);
return sel;
}
BOOL Calculator(void)
{
DWORD sel;
int num1, num2, result;
sel = ShowMenu();
if (sel == EXIT)
return FALSE;
_fputts(_T("Input num1 num2 : "), stdout);
_tscanf(_T("%d %d"), &num1, &num2);
__try
{
switch (sel)
{
case DIV:
Divide(num1, num2);
break;
case MUL:
Multiple(num1, num2);
break;
case ADD:
Add(num1, num2);
break;
case MIN:
Min(num1, num2);
break;
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
_tprintf(_T("Wrong number inserted. try again! \n"));
}
return TRUE;
}
void Divide(int a, int b)
{
_tprintf(_T("%d / %d = %d \n\n"), a, b, a / b);
}
void Multiple(int a, int b)
{
_tprintf(_T("%d * %d = %d \n\n"), a, b, a * b);
}
void Add(int a, int b)
{
_tprintf(_T("%d + %d = %d \n\n"), a, b, a + b);
}
void Min(int a, int b)
{
_tprintf(_T("%d - %d = %d \n\n"), a, b, a - b);
}
실행 결과
Divde 함수에서 발생한 예외를 처리할 구문이 존재하지 않으므로 이를 호출한 Calculator 함수로 예외의 처리가 넘어간다. 만일 Calculator 함수 내에도 예외처리 구문이 존재하지 않으면 _tmain 함수로 예외처리가 이동된다. 그리고 _tmain 함수 내에서도 예외를 처리하지 못한다면 프로그램은 종료된다.
핸들러의 중복
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
enum { DIV = 1, MUL, ADD, MIN, EXIT };
DWORD ShowMenu(void);
BOOL Calculator(void);
void Divide(int, int);
void Multiple(int, int);
void Add(int, int);
void Min(int, int);
int _tmain(int argc, TCHAR* argv[])
{
BOOL state;
do {
state = Calculator();
} while (state == TRUE);
return 0;
}
DWORD ShowMenu(void)
{
DWORD sel;
_fputts(_T("-----Menu----- \n"), stdout);
_fputts(_T("num1 : Divide \n"), stdout);
_fputts(_T("num2 : Multiple \n"), stdout);
_fputts(_T("num 3: Add \n"), stdout);
_fputts(_T("num 4 : Minus \n"), stdout);
_fputts(_T("num 5 : Exit \n"), stdout);
_fputts(_T("SELECTION >>"), stdout);
_tscanf(_T("%d"), &sel);
return sel;
}
BOOL Calculator(void)
{
DWORD sel;
int num1, num2, result;
sel = ShowMenu();
if (sel == EXIT)
return FALSE;
_fputts(_T("Input num1 num2: "), stdout);
_tscanf(_T("%d %d"), &num1, &num2);
__try
{
__try
{
switch (sel)
{
case DIV:
Divide(num1, num2);
break;
case MUL:
Multiple(num1, num2);
break;
case ADD:
Add(num1, num2);
break;
case MIN:
Min(num1, num2);
break;
}
}
__finally
{
_tprintf(_T("End Operation \n\n"));
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
_tprintf(_T("Wrong number inserted. try again! \n\n"));
}
return TRUE;
}
void Divide(int a, int b)
{
_tprintf(_T("%d / %d = %d \n\n"), a, b, a / b);
}
void Multiple(int a, int b)
{
_tprintf(_T("%d * %d = %d \n\n"), a, b, a * b);
}
void Add(int a, int b)
{
_tprintf(_T("%d + %d = %d \n\n"), a, b, a + b);
}
void Min(int a, int b)
{
_tprintf(_T("%d - %d = %d \n\n"), a, b, a - b);
}
실행 결과
핸들러는 위와 같이 중첩해서 사용이 가능하다.
End operation을 출력하는 문장을 __finally 블록으로 감싸지 않으면 예외 발생시에 해당 문자열을 확인할 수 없다.
정의되어 있는 예외의 종류와 예외를 구분하는 방법
예외가 발생했을 때 예외의 종류를 구분하기 위하여 GetExceptioncode 함수를 호출하면 된다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
int _tmain(int argc, TCHAR* argv[])
{
int* p = NULL;
int sel = 0;
int num = 0;
while (1)
{
_tprintf(_T("1 for memory access exception \n"));
_tprintf(_T("2 for divdie by 0 exception \n"));
_tprintf(_T("Select exception type[3 for exit]"));
_tscanf(_T("%d"), &sel);
if (sel == 3)
break;
__try
{
if (sel == 1)
{
*p = 100;
_tprintf(_T("value : %d \n"), *p);
}
else
{
int n = 0;
n = 7 / num;
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
DWORD exptType = GetExceptionCode();
switch (exptType)
{
case EXCEPTION_ACCESS_VIOLATION:
_tprintf(_T("Access violation \n\n"));
break;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
_tprintf(_T("Divide by zero \n\n"));
break;
}
}
}
return 0;
}
실행 결과
GetExceptionCode 함수는 __except 블록 내에서나 예외 필터 표현식을 지정하는 위치에서만 호출 가능하다.
EXCEPTION_CONTINUE_EXECUTION & EXCEPTION_CONTINUE_SEARCH
EXCEPTION_CONTINUE_EXECUTION은 위 예제와 같은 상황에서 나눗셈 연산 시 0이 입력된 것으로 인해 예외 상황이 야기되었으므로 해당 숫자만 다시 입력받는 상황을 가능하게 해준다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
enum { DIV = 1, MUL, ADD, MIN, EXIT };
DWORD ShowMenu(void);
BOOL Calculator(void);
DWORD FilterFunction(DWORD exptType);
int _tmain(int argc, TCHAR* argv[])
{
BOOL state;
do {
state = Calculator();
} while (state == TRUE);
return 0;
}
DWORD ShowMenu(void)
{
DWORD sel;
_fputts(_T("-----Menu----- \n"), stdout);
_fputts(_T("num1 : Divide \n"), stdout);
_fputts(_T("num2 : Multiple \n"), stdout);
_fputts(_T("num 3: Add \n"), stdout);
_fputts(_T("num 4 : Minus \n"), stdout);
_fputts(_T("num 5 : Exit \n"), stdout);
_fputts(_T("SELECTION >>"), stdout);
_tscanf(_T("%d"), &sel);
return sel;
}
int num1, num2, result;
BOOL Calculator(void)
{
DWORD sel;
sel = ShowMenu();
if (sel == EXIT)
return FALSE;
_fputts(_T("Input num1 num2: "), stdout);
_tscanf(_T("%d %d"), &num1, &num2);
__try
{
switch (sel)
{
case DIV:
result = num1 / num2;
_tprintf(_T("%d/%d=%d \n\n"), num1, num2, result);
break;
case MUL:
result = num1 * num2;
_tprintf(_T("%d * %d = %d \n\n"), num1, num2, result);
break;
case ADD:
result = num1 + num2;
_tprintf(_T("%d + %d = %d \n\n"), num1, num2, result);
break;
case MIN:
result = num1 - num2;
_tprintf(_T("%d - %d = %d \n\n"), num1, num2, result);
break;
}
}
__except (FilterFunction(GetExceptionCode()))
{
_tprintf(_T("__except block... \n\n"));
}
return TRUE;
}
DWORD FilterFunction(DWORD exptType)
{
switch (exptType)
{
case EXCEPTION_ACCESS_VIOLATION:
_tprintf(_T("Access violation \n\n"));
return EXCEPTION_EXECUTE_HANDLER;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
_tprintf(_T("Wrong number inserted. \n"));
_tprintf(_T("Input second number again: "));
_tscanf(_T("%d"), &num2);
return EXCEPTION_CONTINUE_EXECUTION;
default:
return EXCEPTION_EXECUTE_HANDLER;
}
}
EXCEPTION_CONTINUE_EXECUTION을 사용하여 0으로 나누는 상황에 인자를 다시 받는 상황을 만들었다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
enum { DIV = 1, MUL, ADD, MIN, EXIT };
DWORD ShowMenu(void);
BOOL Calculator(void);
DWORD FilterFunction(DWORD exptType);
int _tmain(int argc, TCHAR* argv[])
{
BOOL state;
__try
{
do {
state = Calculator();
} while (state == TRUE);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
_tprintf(_T("End of execution! \n"));
}
return 0;
}
DWORD ShowMenu(void)
{
DWORD sel;
_fputts(_T("-----Menu----- \n"), stdout);
_fputts(_T("num1 : Divide \n"), stdout);
_fputts(_T("num2 : Multiple \n"), stdout);
_fputts(_T("num 3: Add \n"), stdout);
_fputts(_T("num 4 : Minus \n"), stdout);
_fputts(_T("num 5 : Exit \n"), stdout);
_fputts(_T("SELECTION >>"), stdout);
_tscanf(_T("%d"), &sel);
return sel;
}
int num1, num2, result;
BOOL Calculator(void)
{
DWORD sel;
sel = ShowMenu();
if (sel == EXIT)
return FALSE;
_fputts(_T("Input num1 num2: "), stdout);
_tscanf(_T("%d %d"), &num1, &num2);
__try
{
switch (sel)
{
case DIV:
result = num1 / num2;
_tprintf(_T("%d/%d=%d \n\n"), num1, num2, result);
break;
case MUL:
result = num1 * num2;
_tprintf(_T("%d * %d = %d \n\n"), num1, num2, result);
break;
case ADD:
result = num1 + num2;
_tprintf(_T("%d + %d = %d \n\n"), num1, num2, result);
break;
case MIN:
result = num1 - num2;
_tprintf(_T("%d - %d = %d \n\n"), num1, num2, result);
break;
}
}
__except (FilterFunction(GetExceptionCode()))
{
_tprintf(_T("__except block... \n\n"));
}
return TRUE;
}
DWORD FilterFunction(DWORD exptType)
{
switch (exptType)
{
case EXCEPTION_ACCESS_VIOLATION:
_tprintf(_T("Access violation \n\n"));
return EXCEPTION_EXECUTE_HANDLER;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
_tprintf(_T("Worng number inserted. \n"));
_tprintf(_T("Input second number again: "));
_tscanf(_T("%d"), &num2);
return EXCEPTION_CONTINUE_SEARCH;
defalut:
return EXCEPTION_EXECUTE_HANDLER;
}
}
실행결과
EXCEPTION_CONTINUE_SEARCH는 다른 곳에 있는 예외 핸들러를 통해서 예외를 처리하라는 의미이다.
함수가 호출된 순서를 바탕으로 예외 핸들러를 찾게 된다. Calculator를 호출한 영역은 _tmain이므로 _tmain 함수의 Calculator 호출 문이 있는 위치에서 예외 핸들러를 찾는다.
실행 결과를 확인해보면, _tmain함수에 있는 예외 핸들러가 실행된 것을 알 수 있다. 그러므로 재입력을 받은 숫자는 의미가 없다.
'윈도우즈 시스템 프로그래밍' 카테고리의 다른 글
윈도우즈 시스템 프로그래밍 18장 - 디렉터리 관련 함수 및 그밖의 함수들 (0) | 2021.08.16 |
---|---|
윈도우즈 시스템 프로그래밍 18장 - 기본적인 파일 처리 함수들 (0) | 2021.08.16 |
윈도우즈 시스템 프로그래밍 17장 - SEH(Structured Exception Handling) / 종료 핸들러(Termination Handler) (0) | 2021.08.12 |
윈도우즈 시스템 프로그래밍 16장 - 가상 메모리(Virtual Memory) (0) | 2021.08.08 |
윈도우즈 시스템 프로그래밍 16장 - 캐쉬와 캐쉬 알고리즘 (0) | 2021.08.08 |