13장. 예외 처리와 C언어와의 링크 지정
예제 13-1) 예외 상황에 대한 대처가 없는 프로그램 사례 → 문제
#include <iostream>
using namespace std;
int getExp(int base, int exp) { // base의 exp 지수승을 계산하여 리턴
int value = 1;
for (int n = 0; n < exp; n++)
value = value * base; // base를 exp번 곱하여 지수 값 계산
return value;
}
int main() {
int v = getExp(2, 3);
cout << "2의 3승은 " << v << "입니다." << endl;
int e = getExp(2, -3);
cout << "2의 -3승은 " << e << "입니다." << endl;
// -> 오류 발생! 1/8이 나와야하는데 -1이 출력된다
}
C++
복사
예제 13-2) if문과 리턴 값을 이용한 오류 처리
•
getExp()의 리턴 값이 오류 상태와 계산값을 함께 표시하는 예민한 코드
#include <iostream>
using namespace std;
int getExp(int base, int exp) { // base의 exp 지수승을 계산하여 리턴
if (base <= 0 || exp <= 0) {
return -1; // 오류 리턴
}
int value = 1;
for (int n = 0; n < exp; n++)
value = value * base; // base를 exp번 곱하여 지수 값 계산
return value;
}
int main() {
int v = 0;
v = getExp(2, 3);
if (v != -1)
cout << "2의 3승은 " << v << "입니다." << endl;
else
cout << "오류. 2의 3승은 " << "계산할 수 없습니다." << endl;
int e = 0;
e = getExp(2, -3); // 2의 -3승? getExp()는 false 리턴
if (e != -1)
cout << "2의 -3승은 " << e << "입니다." << endl;
else
cout << "오류. 2의 -3승은" << "계산할 수 없습니다." << endl;
}
C++
복사
예제 13-3) 리턴 값과 참조 매개 변수를 이용한 오류 처리
•
getExp()의 리턴 값의 단일화 - 오류 상태만 표시
•
참조 매개변수를 통해 계산 값을 전달하는 정리된 코드
#include <iostream>
using namespace std;
int getExp(int base, int exp, int &ret) { // base ** exp 값을 계산하여 ret에 저장
if (base <= 0 || exp <= 0) {
return false;
}
int value = 1;
for (int n = 0; n < exp; n++)
value = value * base; // base를 exp번 곱하여 지수 값 계산
ret = value;
return true;
}
int main() {
int v = 0;
if (getExp(2, 3, v)) // v = 2**3 = 8, getExp()는 true 리턴
cout << "2의 3승은 " << v << "입니다." << endl;
else
cout << "오류. 2의 3승은 " << "계산할 수 없습니다." << endl;
int e = 0;
if (getExp(2, -3, e)) // 2**-3 ? -> getExp()는 false 리턴
cout << "2의 -3승은 " << e << "입니다." << endl;
else
cout << "오류. 2의 -3승은 " << "계산할 수 없습니다." << endl;
}
C++
복사
예외
예외
•
실행 중, 프로그램 오동작이나 결과에 영향을 미치는 예상치 못한 상황 발생
예외 처리기
•
예외 발생을 탐지하고 예외를 처리하는 코드
◦
잘못된 결과, 비정상적인 실행, 시스템에 의한 강제 종료를 막음
예외 처리 수준
•
운영체제 수준 예외 처리
◦
운영체제가 예외의 발생을 탐지하여, 응용프로그램에게 알려주어 예외에 대처하게 함
◦
OS마다 서로 다르므로, OS나 컴파일러 별로 예외 처리 라이브러리로 작성
•
응용프로그램 수준 예외 처리
◦
사용자의 잘못된 입력이나
◦
없는 파일을 여는 등, 응용프로그램 수준에서 발생하는 예외를 자체적으로 탐지하고 처리하는 방법
C++ 예외 처리
•
C++ 표준의 예외 처리
•
응용프로그램 수준 예외 처리
C++ 예외 처리 기본 형식
try-throw-catch
try {} 블록
•
예외가 발생할 가능성이 있는 코드를 묶음
throw문
•
발견된 예외를 처리하기 위해, 예외 발생을 알리는 문장
•
try{} 블록 내에서 이루어져야 함
catch() {} 블록
•
throw에 의해 발생한 예외를 처리하는 코드
try {
....
예외 발견한다면 {
throw XXX; // 예외 발생 알림. XXX는 예외 값
}
예외 발견한다면 {
throw YYY;
}
}
catch(처리할 예외 파라미터 선언) { // catch {} 블록
예외 처리문
}
catch(처리할 예외 파라미터 선언) { // catch {} 블록
예외 처리문
}
C++
복사
throw와 catch
throw 3;
...
catch(int x) { // throw 3; 이 실행되면 catch()문 실행, x에 3이 전달
...
}
C++
복사
예)
try {
throw 3.5;
}
catch(double d) {
...
}
///////////////////
try {
throw "음수 불가능";
}
catch(const char* s) { // const char* 타입의 예외 처리, 얘외 값은 "음수 불가능"이 s에 전달됨
cout << s;
}
C++
복사
예제 13-4) 0으로 나누는 예외 처리
#include <iostream>
using namespace std;
int main() {
int n, sum, average;
while(true) {
cout << "합을 입력하세요>> ";
cin >> sum;
cout << "인원 수를 입력하세요>> ";
cin >> n;
try {
if (n <= 0)
throw n;
else
average = sum / n;
}
catch(int x) {
cout << "예외 발생!! " << x << "으로 나눌 수 없음" << endl;
average = 0;
cout << endl;
continue;
}
cout << "average = " << average << endl << endl;
}
}
C++
복사
하나의 try {} 블록에 다수의 catch() {} 블록 연결
try {
...
throw "음수 불가능";
...
throw 3;
...
}
// 문자열 타입 예외 처리, "음수 불가능"이 s에 전달
catch (const char* s) {
...
}
// int 타입 예외 처리, 3이 x에 전달됨
catch (int x) {
...
}
C++
복사
함수를 포함하는 try {} 블록
try {
int n = multiply(2, -3);
}
catch (const char* negative) {
}
C++
복사
예제 13-5) 지수 승 계산을 예외 처리 코드로 재작성 (완결판)
#include <iostream>
using namespace std;
int getExp(int base, int exp) {
if (base <= 0 || exp <= 0) {
throw "음수 사용 불가";
}
int value = 1;
for (int n = 0; n < exp; n++) {
value = value * base;
return value; // 계산 결과 리턴
}
}
int main() {
int v = 0;
try {
v = getExp(2, 3); // v = 2의 3승 = 8
cout << "2의 3승은 " << v << "입니다." << endl;
v = getExp(2, -3); // 2의 -3승은?
cout << "2의 -3승은 " << v << "입니다." << endl;
}
catch(const char *s) {
cout << s <<endl;
}
}
C++
복사
예제 13-6) 문자열을 정수로 변환하기
#include <iostream>
#include <cstring>
using namespace std;
// 문자열 -> 정수
// 정수로 변환하기 어려운 문자열의 경우, char* 타입 얘외 발생
int stringToInt(const char x[]) {
int sum = 0;
int len = strlen(x);
for (int i = 0; i < len; i++) {
if (x[i] >= '0' && x[i] <= '9')
sum = sum*10 + x[i] - '0';
else
throw x; // char* 타입의 예외 발생
}
return sum;
}
int main() {
int n;
try {
n = stringToInt("123");
cout << "\"123\" 은 정수 " << n << "로 변환됨" << endl;
n = stringToInt("1A3"); // 문자열을 정수로 변환
cout << "\"1A3\" 은 정수 " << n << "로 변환됨" << endl;
}
catch(const char* s) {
cout << s << "처리에서 예외 발생!!" << endl;
return 0;
}
}
C++
복사
예외를 발생시키는 함수의 선언
•
함수 원형에 연이어 throw(예외 타입, 예외 타입, …) 선언
int max(int x, int y) throw(int) {
if (x < 0) throw x;
else if (y < 0) throw y;
else if (x > y) return x;
else return y;
}
double valueAt(double *p, int index) throw(int, char*) {
if (index < 0)
throw "index out of bounds exception"; // char* 타입 예외 발생
else if (p == NULL)
throw 0; // int 타입 예외 발생
else
return p[index];
}
C++
복사
→ 장점
•
프로그램의 작동을 명확히 함
•
프로그램의 가동성 높임
예제 13-7) 예외 처리를 가진 스택 클래스 만들기
#include <iostream>
class MyStack {
int data[100];
int tos;
public:
MyStack() { tos = -1; }
void push(int n) throw(char*);
int pop() throw(char*);
}
void MyStack::push(int n) {
if (tos == 99)
throw "Stack Full";
tos++;
data[tos] = n;
}
int MyStack::pop() {
if (tos == -1)
throw "Stack Empty";
int rData = data[tos--];
return rData;
}
int main() {
MyStack intStack;
try {
intStack.push(100);
intStack.push(200);
cout << intStack.pop() << endl;
cout << intStack.pop() << endl;
cout << intStack.pop() << endl; // "Stack Empty" 예외 발생
}
catch(const char* s) {
cout << "예외 발생 : " << s << endl;
}
}
C++
복사
다중 try {} 블록
•
try {} 블록 내, try {} 블록의 중첩 가능
throw 사용 시 주의사항
•
throw문의 위치
◦
항상 try {} 블록 안에서 실행
▪
시스템이 abort() 호출, 강제 종료
•
예외를 처리할 catch()가 없으면 → 프로그램 강제 종료
•
catch() { } 블록 내에도, try { } catch() { } 블록 선언 가능
예외 클래스 만들기
예외 값의 종류
•
기본 타입의 예외 값
◦
정수, 실수, 문자열 등 비교적 간단한 예외 정보 전달
•
객체 예외 값
◦
예외 값으로 객체를 던질 수 있디
◦
예외 값으로 사용할 예외 클래스 작성 필요
예외 클래스
•
사용자는 자신 만의 예외 정보를 포함하는 클래스 작성
•
throw로 객체를 던짐
◦
객체가 복사되어 예외 파라미터에 전달
예제 13-8) 예외 클래스 만들기
#include <iostream>
#include <string>
using namespace std;
class MyException { // 사용자가 만드는 기본 예외 클래스 선언
int lineNo;
string func, msg;
public:
MyException(int n, string f, string m) {
lineNo = n; func = f; msg = m;
}
void print() { cout << func << " : " << lineNo << " ," << msg << endl; }
};
class DivideByZeroException : public MyException { // 0으로 나누는 예외 클래스 선언
public:
DivideByZeroException (int lineNo, string func, string msg) : MyException(lineNo, func, msg) {}
};
class InvalidInputException : public MyException { // 잘못된 입력 예외 클래스 선언
public:
InvalidInputException(int lineNo, string func, string msg) : MyException(lineNo, func, msg) {}
};
int main() {
int x, y;
try {
cout << "나눗셈을 합니다. 두개의 양의 정수를 입력하세요>> ";
cin >> x >> y;
if (x < 0 || y < 0)
throw InvalidInputException(32, "main()", "음수 입력 예외 발생");
if (y == 0)
throw DivideByZeroException(34, "main()", "0으로 나누는 예외 발생");
cout << (double)x / (double)y;
}
catch(DivideByZeroException &e) {
e.print();
}
catch(InvalidInputException &e) {
e.print();
}
}
C++
복사
C++ 코드에서 C 코드의 링킹
이름 규칙 (naming mangling)
•
컴파일 후 목적 코드에 이름 붙이는 규칙
◦
변수, 함수, 클래스 등의 이름
C언어의 이름 규칙
•
이름 앞에 밑줄표시문자(_)를 붙인다
◦
int f (int x, int y) → _f
◦
int main() → _main
C++의 이름 규칙
•
함수의 매개 변수 타입과 개수, 리턴 타입에 따라 복잡한 이름
C++에서 C 함수 호출 시, 링크 오류 발생
extern “c”
•
C 컴파일러로 컴파일할 것을 지시
◦
C이름 규칙으로 목적 코드를 생성할 것을 지시
사용법
•
함수 하나만 선언
extern "C" int f(int x, int y);
C++
복사
•
여러 함수들 선언
extern "C" {
int f(int x, int y);
void g();
char s(int []);
}
C++
복사
•
헤더 파일 통째로 선언
extern "C" {
#include "mycunction.h"
}
C++
복사