C++

[C++] 예외처리

hanseongbugi 2024. 12. 18. 15:19

exception

  • 프로그램의 실행 도중에 발생하는 문제 상황
  • 컴파일 시 발생하는 문법적인 에러는 예외의 범주에 포함되지 않음
  • 예외 상황의 예시
    1. 나이를 입력하라고 했는데, 0보다 작은 값이 입력
    2. 나눗셈을 위한 두 개의 정수를 입력 바든데, 제수(나누는 수)로 0이 입력
    3. 주민등록번호 13자리만 입력하라고 했더니, 중간에 -를 포함하여 14자리를 입력
  • 예외는 문법적인 오류가 아닌, 프로그램의 논리에 맞지 않는 상황을 의미
#include <iostream>

int main()
{
    int num1, num2;
    std::cout << "두 개의 숫자 입력: ";
    std::cin >> num1 >> num2;

    std::cout << "나눗셈의 몫: " << num1 / num2 << std::endl;
    std::cout << "나눗셈의 나머지: " << num1 % num2 << std::endl;
   
    return 0;
}
output1
두 개의 숫자 입력: 9 2
나눗셈의 몫: 4
나눗셈의 나머지: 1

output2
두 개의 숫자 입력: 7 0
Floating point exception (core dumped)

try, catch, throw

  • 예외처리에서 익숙해져야 할 세가지 키워드
    • try
    • catch
    • throw

try 블록

  • try 블록은 예외 발생에 대한 검사의 범위를 지정할 때 사용
  • try 블록 내에서 예외가 발생하면, C++ 예외처리 매커니즘에 의하여 처리
try {
    // 예외발생 예상지역
}

catch 블록

  • catch 블록은 try 블록에서 발생한 예외를 처리하는 코드가 담기는 영역
  • 마치 반환형이 없는 함수와 유사
catch (처리할 예외의 종류 명시) {
    // 예외처리 코드의 삽입
}

try 블록과 catch 블록

  • catch 블록은 try 블록의 뒤에 이어서 등장
  • try 블록에서 발생한 예외는 catch 블록에서 처리
try {
    // 예외발생 예상지역
}
catch (처리할 예외의 종류 명시) {
    // 예외처리 코드의 삽입
}
  • try와 catch는 하나의 문장
  • 항상 이어서 등장해야 한다.

throw

  • 키워드 throw는 예외가 발생했음을 알리는 문장의 구성에 사용
throw expn;
  • expn은 변수, 상수, 객체 등 표현 가능한 모든 데이터가 될 수 있다.
    • 이때, 예외상황에 대한 정보를 담은 의미 있는 데이터이여야 한다.
    • expn의 위치에 오는 데이터를 예외라고 표현하기도 함
#include <iostream>

int main()
{
    int num1, num2;
    std::cout << "두 개의 숫자 입력: ";
    std::cin >> num1 >> num2;

    try {
        if (num2 == 0) {
            throw num2;
        }
        std::cout << "나눗셈의 몫: " << num1 / num2 << std::endl;
        std::cout << "나눗셈의 나머지: " << num1 % num2 << std::endl;
    }
    catch (int expn) {
        std::cout << "제수는 " << expn << "이 될 수 없습니다." << std::endl;
        std::cout << "프로그램을 다시 실행하세요." << std::endl;
    }
    std::cout << "end of main" << std::endl;
    return 0;
}
output
두 개의 숫자 입력: 7 0
제수는 0이 될 수 없습니다.
프로그램을 다시 실행하세요.
end of main
  • 예외가 발생하면, catch 블록이 실행되고 나서 예외가 발생한 지점 이후를 실행하지 않음
    • catch 블록 이후가 실행된다.

스택 풀기 (Stack Unwinding)

  • myFunc라는 함수 안에서 throw절에 의해 예외가 발생하였다.
  • 함수 내 try-catch 문이 없는 경우
    • 예외처리에 대한 책임은 myFunc를 호출한 영역으로 넘어간다.
#include <iostream>
#include <cstring>
#include <cmath>

int StoI(char* str)
{
    int length = strlen(str);
    int num = 0;

    for (int i = 0; i < length; i++) {
        if (str[i] < '0' || str[i] > '9') {
            throw str[i];
        }
        num += (int)(pow((double)10, (length - 1) - i) * (str[i] + (7 - '7')));
    }
    return num;
}
int main()
{
    char str1[100];
    char str2[200];

    while (1) {
        std::cout << "두 개의 숫자 입력: ";
        std::cin >> str1 >> str2;

        try {
            int result = StoI(str1) + StoI(str2);
            std::cout << str1 << " + " << str2 << \
                         " = " << result << std::endl;
            break;
        }
        catch (char ch) {
            std::cout << "문자 " << ch << "가 입력되었습니다" << std::endl;
            std::cout << "재입력 진행합니다." << std::endl << std::endl;
        }
    }
    std::cout << "프로그램을 종료합니다." << std::endl;
    return 0;
}

output
두 개의 숫자 입력: 123 3A5
문자 A가 입력되었습니다
재입력 진행합니다.

두 개의 숫자 입력: 28F 211
문자 F가 입력되었습니다
재입력 진행합니다.

두 개의 숫자 입력: 231 891 
231 + 891 = 1122
프로그램을 종료합니다.
  • 함수 내에서 함수를 호출한 영역으로 예외 데이터를 전달하면, 해당 함수는 더이상 실행되지 않고 종료가 된다.
    • 함수를 호출한 영역으로 예외 데이터가 전달되는 현상을 스택 풀기라고 한다.
  • 예외가 처리되지 않아, 예외 데이터가 main 함수까지 도달했는데, main에서도 예외를 처리하지 않으면, terminate된다.

여러 개의 catch 블록

  • 하나의 try 블록 내에서 유형이 다른 둘 이상의 예외가 발생할 수 있다.
    • 이 경우 각각의 예외를 표현하기 위해 사용되는 예외 데이터의 자료형이 다를 수 있다.
#include <iostream>
#include <cstring>
#include <cmath>

int StoI(char* str)
{
    int length = strlen(str);
    int num = 0;
    
    if (length != 0 && str[0] == '0') {
        throw 0;
    }

    for (int i = 0; i < length; i++) {
        if (str[i] < '0' || str[i] > '9') {
            throw str[i];
        }
        num += (int)(pow((double)10, (length - 1) - i) * (str[i] + (7 - '7')));
    }
    return num;
}
int main()
{
    char str1[100];
    char str2[200];

    while (1) {
        std::cout << "두 개의 숫자 입력: ";
        std::cin >> str1 >> str2;

        try {
            int result = StoI(str1) + StoI(str2);
            std::cout << str1 << " + " << str2 << " = " << result << std::endl;
            break;
        }
        catch (char ch) {
            std::cout << "문자 " << ch << "가 입력되었습니다" << std::endl;
            std::cout << "재입력 진행합니다." << std::endl << std::endl;
        }
        catch (int expn) {
            if (expn == 0) {
                std::cout << "0으로 시작하는 숫자는 입력불가." << std::endl;
            }
            else {
                std::cout << "비정상적인 입력이 이루어졌습니다." << std::endl;
            }
            std::cout << "재입력 진행합니다." << std::endl << std::endl;
        }
    }
    std::cout << "프로그램을 종료합니다." << std::endl;
    return 0;
}
output
두 개의 숫자 입력: 12A 519
문자 A가 입력되었습니다
재입력 진행합니다.

두 개의 숫자 입력: 082 910
0으로 시작하는 숫자는 입력불가.
재입력 진행합니다.

두 개의 숫자 입력: 123 456
123 + 456 = 579
프로그램을 종료합니다.
  • catch 블록이 둘 이상인 경우
    • 예외 데이터를 전달받을 수 있는 catch 블록을 위에서 아래로 찾아내려 간다.
    • 적절한 catch블록을 찾게 되면, 해당 catch 블록이 실행된다.

모든 예외를 처리하는 catch블록

try {
    // 구현 생략
}
catch (...) {
    // 구현 생략
}
  • ...은 모든 예외를 다 받아주겠다는 선언
  • try 블록 내에서 전달되는 모든 예외가 자료형에 상관없이 걸려든다.
  • 마지막 catch 블록에 덧붙여지는 경우가 많다.

 

 

 

 

 

출처

윤성우 열혈 C++ 프로그래밍