에러 처리

시스템 프로그래밍에서 에러는 함수 리턴 값으로 확인이 가능하며 특수한 변수인 errno로 에러가 발생한 구체적인 이유를 알 수 있다.

정확한 값은 함수에 따라 다르지만, 보통 에러가 발생하면 -1을 반환한다. 이 에러 값은 호출한 측에 에러가 발생했음을 알려주지만, 자세한 에러 이유는 알려주지 않는다. 이때 에러 원인을 찾아내기 위해 errno 변수를 사용한다.

<errno.h>
extern int errno;

이 값은 errno에 값을 대입한 함수를 호출한 직후에만 유효하다. 연이어 다른 함수를 실행하면 errno 값이 바뀌므로 주의해야 한다.

C 라이브러리는 errno 값을 그에 맞는 문자열 표현으로 변환하는 함수를 제공한다.

perror
#include <stdio.h>

void perror(const char *str);

perror 함수는 str이 가리키는 문자열 뒤에 콜론(:)을 붙인 다음에 errno가 기술하는 현재 에러를 문자열로 바꿔 표준 에러(stderr: stanard error)로 내보낸다.

if(close(fd) == -1) 
    perror ("close");

또한, C 라이브러리는 strerror()와 strerror_r() 함수를 제공한다.

strerror
#include <string.h>

char * strerror(int errnum);
strerror_r
#include <string.h>

int strerror_r(int errnum, char *buf, size_t len);

strerror() 함수는 errnum 에러에 대한 설명이 담긴 문자열에 대한 포인터를 반환한다. 문자열 내용을 애플리케이션에서 바꾸면 안 되지만, 연속해서 perror()와 strerror()를 호출하면 바뀔 수도 있다. 이런 의미에서 strerror()는 Thread Safe 하지 않다.

strerror_r() 함수는 스레드에서 사용할 수 있다. 이 함수는 buf가 가리키는 지점부터 len만큼 버퍼를 채운다. 성록하면 0을 반환하고 실패하면 -1 을 반환한다.

어떤 함수에서는 반환 타입의 전체 범위가 유효한 반환값인 경우도 있다. 이런 경우 호출 전에 errno를 0으로 초기화한 후에 함수를 호출하여 errno를 검사한다.

errno = 0;
arg = strtoul(buf, NULL, 0);
if(errno)
    perror("strtoul");

흔히 하는 실수로, 라이브러리나 시스템 콜에서 errno 값을 바꿀 수 있다는 사실을 잊은 채 errno 값을 검사하는 경우가 있다. 예를 들어 다음 코드는 버그가 있다.

if(fsync(fd) == -1) {
    fprintf(stderr, "fsync failed!\n");
    if(errno == EIO) 
        fprintf(stderr, "I/O error on %d!\n", fd);
}

만일 함수를 여러 번 호출하면서 errno 값을 보존해야 한다면 다른 곳에 따로 저장하자.

if(fsync(fd) == -1) {
    const int err = errno;
    fprintf(stderr, "fsync failed: %s\n", strerror(errno));
    if(err == EIO) {
        /* I/O와 관련된 에러라면 빠져나간다 */
        fprintf(stderr, "I/O error on %d!\n", fd);
        exit(EXIT_FAILURE);
    }
}

싱글 스레드 프로그램에서 errno는 전역 변수이지만, 멀티 스레드 프로그램에서 errno는 스레드 별로 저장되므로 스레드에서 사용이 가능하다.

Last updated