Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 러닝 스칼라
- flask
- 딥러닝
- 러닝스칼라
- 웹해킹
- 백엔드
- hackthissite
- Javascript
- c++
- backend
- ChatGPT
- 리눅스
- 챗GPT
- Web
- 인공지능
- BOF
- hackerschool
- Python
- mysql
- php
- deep learning
- Scala
- 경제
- Linux
- 파이썬
- BOF 원정대
- webhacking
- c
- hacking
- Shellcode
Archives
- Today
- Total
jam 블로그
[C++] 013. 예외처리(Exception Handling) 본문
728x90
I. 기존의 예외처리 방식
- 예외를 처리하지 않는 프로그램의 오류
#include <iostream> using namespace std; int main() { int a,b; cout<<"입력 : "; cin>>a>>b; cout<<"a/b의 몫 : "<<a/b<<endl; cout<<"a/b의 나머지 : "<<a%b<<endl; return 0; }
위의 소스는 정말 쉬운 소스이다.
- 하지만 치명적인 오류가 있다. 다들 알다시피 b가 0일때의 문제이기 때문이다.
- 전통적인 스타일의 예외처리
#include <iostream> using namespace std; int main() { int a,b; cout<<"입력 : "; cin>>a>>b; cout<<"a/b의 몫 : "<<a/b<<endl; cout<<"a/b의 나머지 : "<<a%b<<endl; return 0; }
위와 같은 식으로 하면 당연히 오류가 발생하는 것을 막을수는 있다.
하지만 예외처리를 위한 코드 부분과 일반적인 프로그램의 흐름을 위한 코드 부분을 명확히 구분 짓지 못한다.
- 짧은 소스일때는 상관 없지만 길경우 구별하기 힘들다.
II. 기본적인 예외 처리 메커니즘(try, catch, throw)
try
- 예외 발생에 대한 검사 범위를 설정 할 때 사용한다.
try { //예외 발생 예상 코드 }
catch
- 예외를 처리하는 코드 구간을 선언할때 사용한다. try 구간 내에서 발생한 예외 상황을 처리하는 코드가 존재하는 영역이다.
catch(처리되어야 할 예외의 종류) { //예외를 처리하는 코드가 존재할 위치 }
try와 catch
- try바로 뒤에 catch가 등장한다.
throw
- 예외 상황이 발생 하였음을 알릴때 사용
try { if(예외 상황 발생) throw ex; } catch(exception ex) { 예외 상황 처리 }
- 예외 처리 메커니즘의 적용
#include <iostream> using namespace std; int main() { int a,b; cout<<"입력 : "; cin>>a>>b; try { if(b==0) throw b; cout<<"a/b의 몫 : "<<a/b<<endl; cout<<"a/b의 나머지 : "<<a%b<<endl; } catch (int exception) { cout<<exception<<" 입력."<<endl; cout<<"입력 오류! 다시 실행하세요."<<endl; } return 0; }
- 위와 같이 하면 된다.
- 예외 처리 적용 시 프로그램의 흐름
#include <iostream> using namespace std; int main() { int a,b; cout<<"입력 : "; cin>>a>>b; try { cout<<"try block start"<<endl; if(b==0) throw b; cout<<"a/b의 몫 : "<<a/b<<endl; cout<<"a/b의 나머지 : "<<a%b<<endl; cout<<"try block end"<<endl; } catch (int exception) { cout<<"catch block start"<<endl; cout<<exception<<" 입력."<<endl; cout<<"입력 오류! 다시 실행하세요."<<endl; } cout<<"Thank You!"<<endl; return 0; }
- 위 소스를 실행하면 어떻게 흘러가는지 알수 있다.
III. Stack Unwinding(스택 풀기)
- 전달되는 예외
#include <iostream> using namespace std; int divide(int a, int b); int main() { int a,b; cout<<"입력 : "; cin>>a>>b; try { cout<<"a/b의 몫 : "<<divide(a,b)<<endl; } catch (int exception) { cout<<exception<<" 입력."<<endl; cout<<"입력 오류! 다시 실행하세요."<<endl; } return 0; } int divide(int a, int b) { if(b==0) throw b; return a/b; }
처리되지 않은 예외는 전달된다는 것이다.
- 이러한 현상을 가리켜 스택 unwinding이라고 한다.
#include <iostream> using namespace std; void fct1(); void fct2(); void fct3(); int main() { try { fct1(); } catch(int ex) { cout<<"예외 : "<<ex<<endl; } return 0; } void fct1() { fct2(); } void fct2() { fct3(); } void fct3() { throw 100; }
- 예외가 전달되는 과정이 함수의 스택이 풀리는 순서와 일치하기 때문에 스택 unwinding 이라고 한다.
- 처리되지 않은 예외
#include <iostream> using namespace std; int divide(int a, int b); int main() { int a,b; cout<<"입력 : "; cin>>a>>b; cout<<"a/b의 몫 : "<<divide(a,b)<<endl; return 0; } int divide(int a, int b) { if(b==0) throw b; return a/b; }
위와 같은 소스를 실행 시켜서 오류를 나게 해보자
- b가 0이면 예외가 발생을 한다. 하지만 여기서 보면 예외를 처리해 주는 부분이 존재하지 않는다.
- 이러한 경우 stdlib.h 안에 abort 함수가 호출되면서 프로그램을 종료 시킨다.
#include <iostream> #include <stdlib.h> using namespace std; int main() { abort(); cout<<"end"<<endl; return 0; }
- 위와 같은 소스를 실행 시키면 오류메시지를 띄우면서 cout<<"end"<<endl; 위에서 종료가 된다.
#include <iostream> using namespace std; int divide(int a,int b); int main() { int a, b; cout<<"두개 입력 : "; cin>>a>>b; try { cout<<"a/b의 몫 : "<<divide(a,b)<<endl; } catch (char exception) { cout<<exception<<" 입력."<<endl; cout<<"입력 오류!"<<endl; } return 0; } int divide(int a,int b) { if(b==0) throw b; return a/b; }
위의 소스를 보면 예외를 처리해 주는 부분이 있다.
- 다만 catch구문에서 char형 예외를 처리하겠다는 것만 있기 때문에 abort 함수가 호출된다.
전달되는 예외 명시하기
- 함수를 정의하는데 있어서 전달될수 있는 예외의 종류를 명시해 줄 수 있다.
int fct(double b) throw(int) { ... }
상황에 따라서 int형 예외가 전달될 수 있음을 선언하고 있는 것이다.
- int형이 아닌 다른형의 예외가 발생하면 윗부분에서 봤다시피 abort함수가 호출된다.
int fct(double b) throw(int, double, char*) { ... }
- 위에 처럼 둘이상의 예외종류를 선언해 줄 수 있다.
int fct(double b) throw() { ... }
위와 같은 경우는 어떠한 예외도 전달하지 않는다는 것이다.
- 만약에 예외가 전달된다면 abort함수가 호출된다.
- 하나의 try 블록과 여러개의 catch 블록
#include <iostream> using namespace std; int main() { int num; cout<<" input : "; cin>> num; try { if(num>0) throw 10; else throw 'm'; } catch (int exp) { cout<<"int형 예외 발생"<<endl; } catch (char exp) { cout<<"char형 예외 발생 "<<endl; } return 0; }
위의 소스 처럼 0보다 크면 int형 예외, 그 나머지는 char형 예외라고 지정을 해놓았다.
- 위에 처럼 try~catch~catch 이런식으로 선언할수 있는데 역시나 마찬가지로 try와 catch 블록 사이에 다른 문장이 존재할 수 없다.
#include <iostream> using namespace std; char* account="1234-4576"; int sid = 1122; int balance = 1000; class AccountExpt { char acc[10]; int sid; public: AccountExpt(char* str,int id) { strcpy(acc,str); sid=id; } void What() { cout<<"계좌 : "<<acc<<endl; cout<<"비번 : "<<sid<<endl; } }; int main() { char acc[10]; int id; int money; cout<<"계좌번호 입력 :"; cin >> acc; cout<<"비밀번호 입력 : "; cin >> id; if (strcmp(account,acc)||sid!=id) throw AccountExpt(acc,id); cout<<"출금액 입력 : "; cin>>money; if(balance<money) throw money; balance -=money; cout<<"잔액 :"<<balance<<endl; return 0; }
- 위의 소스는 적절하게 예외처리를 안한 상태이다.
#include <iostream> using namespace std; char* account="1234-4576"; int sid = 1122; int balance = 1000; class AccountExpt { char acc[10]; int sid; public: AccountExpt(char* str,int id) { strcpy(acc,str); sid=id; } void What() { cout<<"계좌 : "<<acc<<endl; cout<<"비번 : "<<sid<<endl; } }; int main() { char acc[10]; int id; int money; try { cout<<"계좌번호 입력 :"; cin >> acc; cout<<"비밀번호 입력 : "; cin >> id; if (strcmp(account,acc)||sid!=id) throw AccountExpt(acc,id); } catch(AccountExpt& expt) { cout<<"다시 입력을 확인하세요"<<endl; expt.What(); } try { cout<<"출금액 입력 : "; cin>>money; if(balance<money) throw money; balance -=money; cout<<"잔액 :"<<balance<<endl; } catch(int money) { cout<<"부족 금액 :"<<money-balance<<endl; } return 0; }
실행시켜 보면 알겠지만 예외처리는 해주었다고 하지만 엉망이다.
- 참고로 catch(AccountExpt& expt) 이렇게 한 이유는 객체가 복사되는 부담을 줄이기 위한 것이기 때문에 꼭 이렇게 할 필요는 없다.
#include <iostream> using namespace std; char* account="1234-4576"; int sid = 1122; int balance = 1000; class AccountExpt { char acc[10]; int sid; public: AccountExpt(char* str,int id) { strcpy(acc,str); sid=id; } void What() { cout<<"계좌 : "<<acc<<endl; cout<<"비번 : "<<sid<<endl; } }; int main() { char acc[10]; int id; int money; try { cout<<"계좌번호 입력 :"; cin >> acc; cout<<"비밀번호 입력 : "; cin >> id; if (strcmp(account,acc)||sid!=id) throw AccountExpt(acc,id); cout<<"출금액 입력 : "; cin>>money; if(balance<money) throw money; balance -=money; cout<<"잔액 :"<<balance<<endl; } catch(AccountExpt& expt) { cout<<"다시 입력을 확인하세요"<<endl; expt.What(); } catch(int money) { cout<<"부족 금액 :"<<money-balance<<endl; } return 0; }
위와 같이 try 문에 다 집어 넣어야 정상적으로 된다.
- 이유는 앞전에서 봤을때 비밀번호가 이상하면 예외처리를 부르고 나서 또 다시 그다음에 이어서 실행이 되기때문이다.
IV. 예외 상황을 나타내는 클래스의 설계
예외를 발생시키기 위해서 클래스를 정의하고 객체를 생성하였다.
- 이러한 객체를 예외 객체라고 하며, 예외 객체를 위해 정의되는 글래스를 가리켜 예외 클래스라 한다.
V. 예외를 나타내는 클래스의 상속
catch 블록에 예외가 전달되는 방식
- 이어서 선언되어 있는 catch 블록에 예외가 전달되는 형태를 보면 함수 오버로딩과 유사하다.
- 단, 오버로딩된 함수는 딱 봐서 매개변수가 일치하는 함수가 호출되고 예외를 처리할 catch 블록은 위에서 부터 순차적으로 비교를 이루어지고나서 결정이 난다.
- 상속 관계에 있는 예외 객체의 전달
#include <iostream> using namespace std; class ExceptA { public: void What() { cout<<"ExceptA 예외"<<endl; } }; class ExceptB:public ExceptA { public: void What() { cout<<"ExceptB 예외"<<endl; } }; class ExceptC : public ExceptB { public: void What() { cout<<"ExceptC 예외"<<endl; } }; void ExceptFunction(int ex) { if(ex == 1) throw ExceptA(); else if(ex == 2) throw ExceptB(); else throw ExceptC(); } int main() { int exID; cout<<"발생시킬 예외의 숫자 : "; cin>>exID; try { ExceptFunction(exID); } catch(ExceptA& ex) { cout<<"catch(ExceptA& ex)에 의한 처리"<<endl; ex.What(); } catch(ExceptB& ex) { cout<<"catch(ExceptB& ex)에 의한 처리"<<endl; ex.What(); } catch(ExceptC& ex) { cout<<"catch(ExceptC& ex)에 의한 처리"<<endl; ex.What(); } return 0; }
입력을 1,2,3을 해도 ExceptA에 의한 처리로만 나온다. 왜그럴까?
- 앞에서 말했다시피 catch는 순차적으로 비교를 한다. A를 먼저 비교 할텐데 1,2,3이 예외가 발생하면 각각의 catch에 가겠지만 상속을 받았기 때문에 A에서도 예외가 적용이 되어서 A로만 찍힌다.
- 다음과 같이 바꾸어 보자.
#include <iostream> using namespace std; class ExceptA { public: void What() { cout<<"ExceptA 예외"<<endl; } }; class ExceptB:public ExceptA { public: void What() { cout<<"ExceptB 예외"<<endl; } }; class ExceptC : public ExceptB { public: void What() { cout<<"ExceptC 예외"<<endl; } }; void ExceptFunction(int ex) { if(ex == 1) throw ExceptA(); else if(ex == 2) throw ExceptB(); else throw ExceptC(); } int main() { int exID; cout<<"발생시킬 예외의 숫자 : "; cin>>exID; try { ExceptFunction(exID); } catch(ExceptC& ex) { cout<<"catch(ExceptC& ex)에 의한 처리"<<endl; ex.What(); } catch(ExceptB& ex) { cout<<"catch(ExceptB& ex)에 의한 처리"<<endl; ex.What(); } catch(ExceptA& ex) { cout<<"catch(ExceptA& ex)에 의한 처리"<<endl; ex.What(); } return 0; }
앞전의 소스와 달라진 점은 catch 블록의 순서가 달라졌다.
- 바꾼 이유는 IS-A관계는 역으로 성립하지 않음을 이용한 것이다. 즉, A 예외는 C예외가 아니다.
VI. new 연산자에 의해 전달되는 예외
new 연산자에 의해서 메모리 할당에 실패 했을 경우 NULL포인터가 리턴된다고 했었다.
- C++표준에서는 new 연산자가 메모리 할당에 실패했을 경우 bad_alloc 예외가 전달된다고 한다.
- 자세한건 MSDN을 참조하는게 좋다.
#include <iostream> #include <new> using namespace std; int main() { try { int i=0; while(1) { cout<<i++<<"번째 할당"<<endl; double(*arr)[10000] = new double[10000][10000]; } } catch(bad_alloc ex) { ex.what(); cout<<endl<<"End"<<endl; } return 0; }
위의 소스를 실행 시켜 보면 무한루프를 돌면서 메모리 공간만 할당하고 있다.
- 어느 순간 new 연산이 실패로 돌아가면 bad_alloc 예외가 발생하고 END를 찍게된다.
VII. 예외처리에 대한 나머지 문법 요소
- 모든 예외를 처리하는 catch 블록
try { } catch(...) { }
- 위에서 보면 "..." 의 선언은 모든 예외를 다 처리하겠다는 선언이다.(잘 사용하지는 않는다.)
- 예외 다시 던지기
#include <iostream> using namespace std; class Exception { public: void what() { cout<<"Simple Exception"<<endl; } }; void ThrowException() { try { throw Exception(); } catch(Exception& t) { t.what(); throw; } } int main() { try { ThrowException(); } catch(Exception& t) { t.what(); } return 0; }
먼저 ThrowException()에서 예외를 처리하고 catch에서 throw로 예외를 던졌다.
- 던져진 예외는 메인에 있는 ThrowException();으로 가고 그다음에 있는 catch에서 다시 한번 예외가 처리되어 Simple Exception이 2번 찍힌다.
다음과 같은 경우 예외를 다시 던질 것을 고려해 보자.
- catch 블록에 의해 예외를 잡고 보니, 처리하기가 마땅치 않다. 다른 catch블록에 의해서 예외가 처리되길바란다.
- 하나의 예외에 대해서 처리되어야 할 영역(예외가 발생했음을 알려줘야 할 영역)은 둘 이상이다.
'개발 및 관련 자료 > C' 카테고리의 다른 글
[C++] 012. 템플릿(Template) (0) | 2013.05.12 |
---|---|
[C++] 011. string 클래스 디자인 (0) | 2013.05.12 |
[C++] 010. 연산자 오버로딩 (0) | 2013.05.12 |
[C++] 009. virtual의 원리와 다중 상속 (0) | 2013.05.12 |
[C++] 008. 상속과 다형성 (0) | 2013.05.12 |
Comments