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 | 31 |
Tags
- Python
- BOF 원정대
- 파이썬
- 러닝 스칼라
- 경제
- php
- 인공지능
- Javascript
- Shellcode
- webhacking
- c
- hackthissite
- BOF
- 리눅스
- Linux
- 챗GPT
- 백엔드
- hackerschool
- Web
- deep learning
- flask
- mysql
- c++
- Scala
- 웹해킹
- 러닝스칼라
- backend
- 딥러닝
- ChatGPT
- hacking
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