예외 처리 (Exception Handling)

  • 예외(Exception): 프로그램 실행 중 비정상적으로 발생환 상황

  • 예외 처리(Exception Handling): 예외를 처리하여 프로그램이 정상적으로 동작하도록 하는 것

  • C++에서는 try ... catch 구문을 사용하여 예외를 처리함.

try {
    if (예외 조건)
        throw 예외 객체;
} catch (예외 객체) {
    예외 처리
}
  • try { } 와 catch { } 는 하나로 사용되어야 함.

    • try 블록에서 처리할 예외를 정의.

    • catch 블록에서 발생한 예외를 처리.

  • throw 키워드는 예외를 고의적으로 발생시키는 것.

  • 예외가 발생하는 상황: 0으로 나누기 등

#include <iostream>
using namespace std;

int main() {
    int a = 9, b = 0, c = a / b;
    cout << c << endl;
    return 0;
}

/*
$ g++ test.cpp
$ ./a.out
[1]    1884 floating point exception  ./a.out
 */
  • 예외 처리를 try ... catch 구문으로 하지 않으면 if 문을 사용해야 하므로, 조건문과 예외 처리 코드가 구분되지 않음. 가독성이 떨어짐.

#include <iostream>
using namespace std;

int main() {
    int a = 9, b = 0, c = 0;
    if (b != 0) {
        c = a / b;
        cout << c << endl;
    } else {
        cout << "Exception: Divide by zero" << endl;
    }
    return 0;
}
  • 예외 처리만 명확하게 구분하기 위해 반드시 예외는 try ... catch 구문으로 처리해야 함.

#include <iostream>
using namespace std;

int main() {
    int a = 9, b = 0, c = 0;
    try {
        if (b == 0) throw b;
        c = a / b;
        cout << c << endl;
    } catch (int divided) {
        cout << "Exception: Divide by " << divided << endl;
    }
    return 0;
}

/*
$ g++ test.cpp
$ ./a.out
Exception: Divide by 0
 */
  • 예외가 둘 이상일 경우 catch 를 여러 개 사용해서 처리할 수 있음.

try {
    if (예외 조건1)
        throw 예외 객체1;
    if (예외 조건2)
        throw 예외 객체2;
} catch (예외 객체1) {
    예외 처리
} catch (예외 객체2) {
    예외 처리
}
  • 함수를 사용해서 예외 처리를 할 경우

#include <iostream>
#include <string>
using namespace std;

int divide(int a, int b) {
    if (b == 0)
        throw string("Divide by zero");
    return a / b;
}

int main() {
    int a = 9, b = 0, c = 0;
    try {
        c = divide(a, b);
        cout << c << endl;
    } catch (string msg) {
        cout << "Exception: " << msg << endl;
    }
    return 0;
}
  • throw와 noexcept 키워드로 함수의 예외 처리 범위를 지정할 수 있음.

    • void func(int a); 모든 타입의 예외가 발생 가능.

    • void func(int a) throw(int); 정수형 타입 예외만 던질 수 있음.

    • void func(int a) throw(char *, int); 둘 이상의 타입으로 예외를 던질 때는 쉼표(,)로 나열.

    • void func(int a) throw(); 예외가 발생하지 않음.

    • void func(int a) noexcept; 예외가 발생하지 않음 (C++11 style)

 

표준 예외 클래스 (Standard Exception Class)

  • C++에서는 자주 발생하는 예외에 대해 미리 정의한 클래스를 제공함.

  • 각각의 예외별로 다른 header 파일에 정의되어 있지만, <exception> 헤더 파일을 포함하면 대부분 사용 가능.

  • 대표적인 표준 예외 클래스

    • bad_alloc 메모리 할당 오류로서 new 연산에서 발생 #include <new>

    • bad_cast 형변환 오류로서 dynamic_cast에서 발생 #include <typeinfo.h>

    • bad_type_id typeid에 대한 피 연산자가 널 포인터인 경우 발생

    • bad_exception 예기치 못한 예외로서 함수 발생 목록에 있지 않는 예외

    • bad_logic_error 클래스 논리 오류

    • invalid_argument, length_error, out_of_range 의 기본 #include <stdexcept>

    • runtime_error 실행 오류로 overflow_error와 underflow_error의 기본 #include <stdexcept>

#include <iostream>
#include <new>
using namespace std;

int main () {
    try {
        int* arr= new int[10000000000000000];
        delete[] arr;
    } catch (bad_alloc& ba) {
        cerr << "bad_alloc caught: " << ba.what() << endl;
    }
    return 0;
}

/*
$ ./a.out
a.out(7097,0x117dcadc0) malloc: can't allocate region
:*** mach_vm_map(size=40000000000000000, flags: 100) failed (error code=3)
a.out(7097,0x117dcadc0) malloc: *** set a breakpoint in malloc_error_break to debug
bad_alloc caught: std::bad_alloc
 */

 

스택 풀기 (Stack Unwinding)

  • 예외가 발생했을 때 catch 를 사용하지 않으면 해당 함수를 호출한 위치로 예외를 전달함.

  • 함수 호출 시 정보를 스택에 저장하므로, catch를 만날 때까지 스택에서 함수 정보를 하나씩 꺼내면서 찾아감.

  • 예외 처리를 하기 위해 발생 지점부터 처리 위치까지 스택에서 함수를 소멸시키면서 이동.

#include <iostream>
using namespace std;

void fn1() { throw 0; }
void fn2() { fn1(); }
void fn3() { fn2(); }
void fn4() { fn3(); }

int main () {
    try {
        fn4();
    } catch (int e) {
        cout << "Exception: " << e << endl;
    }
    return 0;
}

/*
$ ./a.out
Exception: 0
 */

 

+ Recent posts