시스템 = CPU + 운영체제 + App
사용자 관점에서 CPU, OS는 내부, App은 외부.
CPU 입장에서는 CPU는 하드웨어이고 OS, App은 소프트웨어.
CPU는 자신이 순차적으로 실행하는 명령어가 OS 코드인지, App 코드인지 관심없고 연산만 할 뿐이다.
CPU는 동일한 바이트끼리의 연산만 가능하고, 4Byte 단위로 연산.
1Byte + 1Byte = 기본적으로 불가능. 내부적으로는 32비트로 변환되어서 연산
4Byte / 1Byte = CPU가 내부적으로 1Byte를 4Byte로 바꿔서 연산
N / 0 = 불가능.
CPU는 위와 같은 상황들은 예외 상황으로 간주. CPU는 몇 가지 예외 상황들을 정해두는데, CPU 위에서 동작하는 소프트웨어(OS + App)에서 예외 상황이 발생할 경우 발생했다는 사실만 알려준다. 이를 어떻게 처리할지는 소프트웨어에게 맡김.
핸들러: 소프트웨어가 예외 상황을 처리하는 코드
소프트웨어는 각 핸들러를 미리 마련해둔다. CPU가 1번 예외 상황이 발생했다고 알릴 경우, 1번 핸들러가 동작. 핸들러 쪽은 소프트웨어 영역에서 사용자가 결정권을 가지고 있고, 예외 상황은 하드웨어 쪽에서 고정된 상황이기 때문에 사용자가 관여할 수 없다.
그러나 하드웨어 쪽 예외 상황을 어떻게 처리할지에 대한 결정권은 소프트웨어에게 있다.
핸들러는 OS에 등록되어 있다. 따라서 OS를 설치할 때 핸들러도 동반됨.
CPU에서 1~4번의 예외 상황이 있을 수 있다고 명시.
OS는 1~4번 예외 상황에 대한 핸들러를 만듦.
Windows라는 운영체제에서는 SEH 기법이라는 매커니즘을 담고 있음. CPU가 예외 발생을 알림. OS 관점에서는 이를 처리해야하는데, 해당 상황을 명령한 건 OS일 수도, App일 수도 있음. 만약 App이 명령한 건일 경우, OS는 해당 예외 상황을 App에게 알리고 처리할 수 있도록 해결 방법을 제공해야 함. 이처럼 예외 상황 발생을 알리는 것이 SEH 매커니즘.
OS 관점에서는 CPU가 예외 상황을 정의하는 것이 불합리하다고 생각. 따라서 OS도 예외 상황을 정의하는 것이 가능. 같은 맥락으로 App도 정의 가능.
따라서 예외 상황은 CPU, OS, App 모두 정의할 수 있다.
하드웨어 예외: CPU가 정의한 예외
소프트웨어 예외: OS, App이 정의한 예외
예외가 발생한 시발점이 CPU면 하드웨어 예외라 하고, OS면 소프트웨어 예외라고 한다.
FILE* ptrFile = fopen("test.txt", "r");
if (NULL == ptrFile) // 파일이 없는 경우는 일반적이진 않음. -> 예외처리
{
// Exception Handling.
}
char* dataBuffer = (char*)malloc(sizeof(char)*100);
if (NULL == dataBuffer) // 동적할당 받을 메모리가 없는 경우는 일반적이진 않음. -> 예외처리
{
// Exception Handling.
}
int nRead = fread(dataBuffer, 1, 10, ptrFile);
if (10 != nRead) // 읽은 데이터의 갯수가 올바르지 않은 경우는 일반적이진않음.->예외처리
{
// Exception Handling.
// 추가로 파일의 끝인지, EOF(end of file)인지 비교해야 함.
}
// 프로그램의 실제 흐름
FILE* ptrFile = fopen("test.txt", "r");
char* dataBuffer = (char*)malloc(sizeof(char)*100);
int nRead = fread(dataBuffer, 1, 10, ptrFile);
// 위 흐름에 대한 예외 처리 영역
if (NULL == ptrFile)
{
// Exception Handling.
}
if (NULL == dataBuffer)
{
// Exception Handling.
}
if (10 != nRead)
{
// Exception Handling.
}
SEH 메커니즘은 사용자의 안정적인 개발을 위해 다양한 것들을 제공. 예외 핸들러를 바탕으로 확장해 종료 핸들러를 제공하는데, 이를 이용하면 프로그램을 좀 더 안정적으로 개발할 수 있다.
try 블럭 안으로 들어올 경우 빠져나갈 때 반드시 finally 블럭을 실행.
__try
{
// 예외 발생 경계 블럭
... 중략 ...
}
__finally
{
// 종료 처리 블럭
... 중략 ...
}
... 중략 ...
__try
{
_tprintf(_T("Input divide string [a / b]: "));
_tscanf(_T("%d / %d", &a, &b));
if (0 == b) { return -1; } // return 한다해도 반환 전 무조건 __finally 블럭 진입.
}
__finally
{
_tprintf(_T("In __finally block.\n"));
}
_tprintf(_T("result: %d\n", a / b));
... 중략 ...
__try
{
ptrFile = _tfopen(_T("string.dat"), _T("a+t")); // 파일 오픈
if (NULL == ptrFile)
{
DWORD strLen = 0;
return -1;
}
strBuffer = (TCHAR*)malloc((strLen + 1) * sizeof(TCHAR));
if (NULL == strBuffer) // 만약 파일을 이미 오픈했는데 메모리 할당 전 문제가 생겨서 리턴되는 상황이 온다면, 리턴 전 finally 영역에서 파일을 close 및 메모리를 반환해줄 것.
{
return -1;
}
... 중략 ...
}
__finally // 반드시 실행되어야 할 코드를 finally 구문으로 묶어두면 코드 실행을 보장받을 수 있음.
{
if (NULL != ptrFile)
{
fclose(ptrFile); // 파일을 열었기에 반드시 닫아줌.
}
if (NULL != strBuffer)
{
free(strBuffer); // 동적할당 받았기에 반드시 해제 해줘야함.
strBuffer = NULL;
}
}
try 블럭에서 발생한 예외는 그 뒤에 나오는 except 블럭에서 처리될 것.
필터: 예외를 처리하는 방법으로 크게 세 가지가 있다. except 블럭에 위치한다.
try 블럭에서 예외가 발생하면 Windows에 의해 except 블럭으로 실행이 옮겨지고, 괄호 안의 필터를 확인 후 알맞은 방식으로 예외를 처리한다.
except 블럭에서 얘기하는 방식대로 실행이 될 때 예외가 처리됐다고 말한다.

EXCEPTION_EXECUTE_HANDLER예외 발생 이후의 try ~ except 블럭은 건너뛰고 except 블럭 이후부터 실행한다.
(*p = 100)

__try
{
switch(sel)
{
... 중략 ...
case DIV:
result = num1 / num2; // 여기서 문제가 생길 경우 끊고 __except 블럭으로 넘어감. 뒤는 의미없음.
_tprintf(_T("%d / %d = %d\n\n"), num1, num2, num1/num2);
break;
... 중략 ...
}
}
// 필터 인지 후 except 내부 코드 실행.
__except(EXCEPTION_EXECUTE_HANDLER)
{
_tprintf(_T("Wrong number inserted. Try again!\n\n"));
}
SEH 메커니즘은 예외가 발생했는데 처리되지 않았다면 해당 예외를 스택이 쌓여있는 역순으로 전달한다.
1) a가 10, b가 0일 경우 Divide() 함수 내에서 예외 발생. 하드웨어가 이를 인지
2) 하드웨어는 OS에게 예외 발생 사실을 알림. OS도 SEH 동작으로 사용자에게 알림.
3) 일반적인 예외 메커니즘과 달리 현재 예외 발생 지점이 try 블럭으로 묶여있지 않음. 이 경우 예외 지점을 포함하고 있는 함수가 전부 반환됨.
4) Divide 에서 예외가 발생했기 때문에 해당 스택이 반환되고, Divide() 함수가 호출되기 전으로 흐름이 돌아옴. 현재 흐름은 Calculator() 함수 내부.
5) 현재 예외가 처리되지 않은 상태이기 때문에 try 블럭을 찾고, except로 가서 필터에 맞게 예외를 처리.
6) 만약 예외를 처리할 except 블럭이 없었다면 Calculator() 함수 반환 후 main()으로 넘어감. main() 함수에도 예외처리 구문이 없다면 main() 함수를 호출한 대상인 운영체제로 넘어감. 이 경우 운영체제는 프로세스를 강제 종료.

except 블럭 내에서는 예외가 왜 발생했는지 알아야 한다.
GetExceptionCode(): 해당 함수의 반환 값을 통해 예외가 발생한 이유를 알 수 있음
호출 가능 위치
: except 블럭 내, 예외 필터 표현식 지정 위치
반환 값의 예
: EXCEPTION_STACK_OVERFLOW
, EXCEPTION_INT_DIVIDE_BY_ZERO
, MSDN 참조
__try
{
... 중략 ...
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
DWORD exceptionType = GetExceptionCode();
// 예외 발생 이유에 따라 예외를 처리
switch(exceptionType)
{
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;
}
}
EXCEPTION_CONTINUE_SEARCHexcept 블럭을 실행하지 않고, 예외가 발생한 지점에서부터 다시 실행.
EXCEPTION_CONTINUE_SEARCH웬만하면 사용 X.
예외 발생 시 해당 함수를 스택에서 그냥 제외한 뒤 이전 실행 위치에서 예외를 처리.
예외가 발생한 위치와 예외 처리 위치를 달리 할 때 사용됨.
