Devlog

[유닉스 C 프로그래밍] errno를 활용해 에러 처리를 해보자 본문

OS/유닉스 C 프로그래밍

[유닉스 C 프로그래밍] errno를 활용해 에러 처리를 해보자

recoma 2022. 5. 28. 19:22
728x90
개발 환경
OS: Ubuntu 20.04 LTS (WSL2)
Compiler: GCC 9.4.0 (POSIX  C)
IDE: Vim, Visual Code

개요

파이썬에서 에러를 잡을 때는 try/except문을 사용합니다. 그리고 에러에 관련된 내용을 알 고 싶을 땐 "as e"를 추가해서 변수 "e"로부터 내용을 분석합니다.

try:
    do_something()
except Exception as e:
    print(e)

반대로 에러를 일으킬 수도 있는 데, 이때 "raise"문과 "Exception" 객체를 사용합니다. raise 문을 호출하면 해당 함수는 그 자리에서 바로 종료 합니다.

def do_something(n):
    if n < 0:
        raise ValueError("Get Value Error")
    return n

파이썬 뿐만 아니라 Java, Golang 등 수많은 언어들이 Exception과 try/catch문을 사용함으로써 불상시에 일어나는 에러는 안전하게 처리할 수 있게 지원을 해주고 있습니다. 하지만 대부분의 언어들은(적어도 Python/Java/Golang) 예외 처리를 하는 데 클래스를 수반합니다. 그렇다면 객체지향 개념이 없는 C언어에서는 어떻게 에러 처리를 할 수 있을 까요?

 

UNIX 함수들이 에러를 표현하는 방법은 크게 두 가지가 있습니다.

  1. -1또는 NULL을 리턴하고 errno 변수 덮어쓰기
  2. 아예 에러코드를 리턴값으로 잡는다.

2번의 경우 함수를 사용하기 전 에러 코드를 파악하고 알아서 처리하면 되지만 1번은 errno변수 및 이와 관련된 함수를 사용법을 알아야 하므로 여기서는 1번만 설명합니다.

perror()

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int
main(void)
{

    int file = 100;
    // perror 출력
    if(close(file) == -1)
        perror("에러 발생");
    
    return 0;
}

위의 예제는 닫혀 있는 파일 디스크립터(100번)을 다시 닫으려고 하는 코드 입니다. 이미 닫혀 있는데 또 닫으려고 하기 때문에 에러가 발생합니다. close() 함수에 에러가 발생하면 -1을 리턴하므로 if문을 잡고 에러가 발생하면 perror()를 호출합니다. 

perror(char*)는 에러 내용을 호출합니다. 문자열을 입력받는데 문자열 그대로 출력하는 것이 아니라 문자열 뒤에 에러 내용을 출력합니다. 에러 내용은 errno변수를 보고 판단합니다. 해당 코드의 출력 결과는 다음과 같습니다.

에러 발생: Bad file descriptor

 

strerror()

perror()가 말 그대로 문자를 프린팅 했다면 strerror는 errno를 인자로 받고 errno에 해당되는 에러 메세지를 반환합니다. 그렇기 때문에 더 세세한 오류 메세지를 작성할 수 있습니다.

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int
main(void)
{

    int file = 100;
    // strerror 출력
    if(close(file) == -1)
        fprintf(stderr, "해당 파일 디스크립터 %d번 에서 오류 발생: %s\n",
                file, strerror(errno));
    
    return 0;
}
해당 파일 디스크립터 100번 에서 오류 발생: Bad file descriptor

직접 에러를 발생시키기 (raise Exception)

아까 perror()가 errno를 토대로 에러를 출력 했고, strerror()가 errno값을 보고 에러 메세지를 리턴 했습니다. 즉, errno 값을 읽고 이에 맞는 메세지를 골랐다는 의미인데, 반대로 errno를 덮어 쓸 수 도 있습니다. 즉, 파이썬의 raise문 처럼 직접 에러 내용을 설정할 수 있습니다.

#include <stdio.h>
#include <errno.h>
#include <string.h>

int
count(int n)
{ 
    if(n < 0)
    {
        // 음수면 EINVAL 에러 호출
        errno = EINVAL;
        return -1;
    }
    else
        return n + 1;
}


int
main(void)
{
    int x = -1;
    if(count(x) == -1)
        fprintf(stderr, "에러 발생: %s, 에러 변수: %d\n", strerror(errno), x);
    return 0;
}
에러 발생: Invalid argument, 에러 변수: -1

count함수는 값을 1씩 올려주는 함수 입니다. 그러나 0 이상의 정수만 사용할 수 있기 때문에 0 미만인 경우(n < 0)는 errno에 EIVAL값을 삽입하고 -1를 리턴합니다. 이렇게 단순히 -1만 리턴하는 것이 아니라 errno까지 정해주면 어디서 어느 부분이 에러가 발생 했는 지 파악하기가 쉬워집니다.

strerror()는 errno의 값을 바꿀 수 있다.

하지만 이렇게 쉽게 에러 메세지를 만들어 주는 strerror에도 흠이 있습니다. 바로 자신이 에러가 발생하면 errno값을 바꿉니다. 그래서 strerror를 사용한 후에도 다른 곳에서 쓰기 위해 errno 값을 유지해야 한다면, 임시 메모리에 저장한 다음 strerror 수행이 끝나면 다시 errno 변수에 덮어쓰면 됩니다.

error = errno;
fprintf(stderr, "에러 발생: %s, 에러 변수: %d\n", strerror(errno), x);
// 저장한 errno 데이터를 errno로 저정하면 error 코드를 유지해서 사용할 수 있다.
errno = error;

(번외) 매크로를 활용한 Error Log 찍기

에러를 찾아서 내용을 출력하는 것도 좋은데... 문제는 이걸 개발 중에서나 써야지 배포판에서는 에러 로그를 찍기에는 보안에 좋지 않습니다. 따라서 매크로를 이용해 Debug모드일 때만 출력하게 해 줍니다.

// ... 생략 ...

int
main(void)
{
    int x = -1;
    int error;
    if(count(x) == -1)
    {
#ifdef DEBUG    // 디버그 모드에서만 작동한다.
        /*
            strerror는 출력할 버퍼가 없거나, error가 없으면 errno를 바꾼다
            따라서 이를 예방하려면 errno를 다른 변수에 저장해야 한다.
        */
        error = errno;
        fprintf(stderr, "에러 발생: %s, 에러 변수: %d\n", strerror(errno), x);
        // 저장한 errno 데이터를 errno로 저정하면 error 코드를 유지해서 사용할 수 있다.
        errno = error;
#endif
    }
    return 0;
}

#ifndef, #endif를 이용해서 DEBUG가 정의될 때만 사용한 것 까진 좋은데 문제는 "#define DEBUG"가 없는데 어떻게 사용하느냐 입니다. 이 경우는 컴파일을 할 때 "-D DEBUG"를 플래그로 추가하면 자동으로  "DEBUG"가 선언됩니다.

gcc main.c -D DEBUG

errno 코드 리스트

errno의 코드는 POSIX ANSI 둘다 쓸 수 있지만 POSIX의 경우 일부 코드가 추가되었습니다.

아래 링크는 POSIX errno 리스트 입니다.

 

131 Linux Error Codes for C Programming Language using errno

131 Linux Error Codes for C Programming Language using errno by Sasikala on October 18, 2010 Programmers should handle all kinds of errors to protect the program from failure. In C programming language, there is no direct support for error handling. You ha

www.thegeekstuff.com

 

틀린 부분이 있으면 지적 부탁드려요~

728x90
반응형