8. C library functions

C 표준의 기능을 둘로 나누자면 하나는 C언어이고 하나는 C 라이브러리이다. C 라이브러리는 프로그래밍에서 자주 필요한 기능들에 대해 깔끔한 인터페이스와 포터블한 구현을 제공한다.

8.1. General properties of the C library and its functions.

라이브러리 함수의 목적은 하나 또는 둘이다.

플랫폼 추상 레이어: 플랫폼별로 필요한 특성들에 대한 추상화를 제공.

기본적인 도구: C 프로그래밍에서 자주 등장하는 기능들에 대한 구현을 제공하는, 고정된 인터페이스의 함수.

8.1.1. Headers.

C 라이브러리에는 많은 함수가 있다. 헤더 파일은 여러 특성들 (그 중 대부분은 함수들이다)에 대한 인터페이스를 기술한다.

8.1.2. Interfaces.

대부분의 C 라이브러리 인터페이스는 함수들이지만 함수형 매크로로 제공되기도 한다.

8.1.3. Error checking.

C 라이브러리 함수는 보통 다음과 같이 특정한 리턴 값으로 실패를 표현한다.

if (puts("hello world") == EOF) {
    perror("can't output to terminal");
    exit(EXIT_FAILURE);
}

Takeaway 1.8.1.1. 실패의 가능성은 항상 존재한다.

Takeaway 1.8.1.2. 라이브러리 함수의 리턴값을 통해 에러를 체크하라.

Takeaway 1.8.1.3. 실패할 것이면 빨리, 앞부분에서, 자주 실패하라.

C 라이브러리에서 많이 쓰이는 상태 변수는 errno이다.

void puts_safe(char const s[static 1]) {
    static bool failed = false;
    if (!failed && puts(s) == EOF) {
        perror("can't output to terminal:");
        failed = true;
        errno = 0;
    }
}

8.1.4. Bounds-checking interfaces.

많은 C 라이브러리 함수들은 버퍼 오버플로우에 취약하다. C11에서는 이를 보완하는 경계-체크 인터페이스를 제공한다. 이에 대한 지원 여부는 __STDC__LIB_EXT1__ 로 체크할 수 있고 __STDC_WANT_LIB_EXT1__로 활성화할 수 있다.

#if ! __STDC_LIB_EXT1__
#error " This code needs bounds checking interface Annex K"
#endif
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
/* Use printf_s from here on. */

Takeaway 1.8.1.4. _s로 끝나는 식별자명은 예약되어 있다.

경계-체크 인터페이스 함수들은 경계 체크가 위반되었을 때 런타임 조건 위반을 발생시켜 프로그램 실행을 끝낸다.

8.1.5. Platform preconditions.

Takeaway 1.8.1.5. 실행 플랫폼에 대한 사전 조건을 충족하지 못하는 경우 컴파일 단계에서 에러를 내야 한다.

이는 전처리 조건문을 통해 강제할 수 있다.

Takeaway 1.8.1.6. 전처리 조건문에서는 매크로와 정수형 리터럴만 평가하라.

Takeaway 1.8.1.7. 전처리 조건문에서 미확인 식별자는 0으로 평가된다.

static_assert로 체크할 수도 있다.

#include <assert.h>
static_assert(sizeof(double) == sizeof(long double), "Extra precision needed for convergence.");

8.2. Mathematics.

수학 함수는 math.h 헤더에 정의되어 있지만, tgmath.h에 정의된 타입-제네릭 매크로를 쓰는 것이 더 간편할 수 있다. 이 함수들은 잘 최적화되어 있기 때문에, 이를 쓰지 않고 직접 구현하는 것은 보통 좋은 생각이 아니다.

8.3. Input, output, and file manipulation.

8.3.1. Unformatted text output.

기본적인 문자열이나 문자를 출력하는 데에는 puts, putchar를 쓴다.

int putchar(int c);
int puts(char const s[static 1]);

영구적 저장소에 출력하려면 FILE* 타입의 스트림을 이용한다.

int fputc(int c, FILE* stream);
int fputs(char const s[static 1], FILE* stream);

FILE은 불명확 타입이다. 이름은 FILE이라고 되어 있지만 딱히 파일을 나타내는 타입은 아니다. 스트림이다.

Takeaway 1.8.3.1. 불명확 타입은 함수형 인터페이스를 통해 특정화된다.

Takeaway 1.8.3.2. 불명확 타입의 세부 구현에 의존하지 마라.

fputs는 puts와 달리 개행 문자를 넣지 않는다.

8.3.2. Files and streams

실제 파일에 출력을 하고 싶다면 fopen으로 파일을 열어 프로그램의 실행과 파일을 연결시켜야 한다.

FILE* fopen(char const path[static 1], char const mode[static 1]);
FILE* freopen(char const path[static 1], char const mode[static 1], FILE* stream);
errno_t fopen_s(FILE* restrict streamptr[restrict], char const filename[restrict],
    char const mode[restrict]);
errno_t freopen_s(FILE* restrict newstreamptr[restrict], char const filename[restrict],
    char const mode[restrict], FILE* restrict stream);

이는 다음과 같이 사용한다. “a”는 파일의 내용에 출력 내용을 덧붙인다는 뜻이다. “w”는 덮어쓰기, “r”은 읽기이다.

int main(int argc, char* argv[argc + 1]) {
    FILE* logfile = fopen("mylog.txt", "a");
    if (!logfile) {
        perror("fopen failed");
        return EXIT_FAILURE;
    }
    fputs("feeling fine today\n", logfile);
    return EXIT_SUCCESS;
}

스트림을 다루는 3개의 다른 인터페이스가 존재한다: 스트림을 다른 파일에 연결시키는 freopen, 그리고 fclose, fflush.

int fclose(FILE* fp);
int fflush(FILE* stream);

8.3.3. Text IO.

텍스트 스트림에 대한 출력은 버퍼되어 있다. 즉, 리소스의 효율적 활용을 위해서, 스트림에 바로 출력하지 않고 버퍼에 저장했다가 버퍼가 꽉 차면 출력한다. fclose로 스트림을 닫으면, 모든 버퍼는 플러시되어 출력된다. fflush는 스트림을 닫지는 않지만 스트림에 연결된 버퍼를 플러시한다. 입출력 버퍼링의 가장 흔한 형태는 라인 버퍼링이다.

프로그램에 입력되는 문자들과 콘솔/파일에 출력되는 바이트는 일대일 대응이 되지 않는다.

Takeaway 1.8.3.4. 텍스트 입력과 출력은 데이터를 변환한다.

이는 문자에 대한 내부/외부 표현이 항상 같지는 않기 때문이다. 같은 문자에 대한 인코딩도 여러 가지일 수 있다. 가장 악명높은 예는 개행 문자이다.

Takeaway 1.8.3.5. 개행 문자에는 자주 쓰이는 3개의 변환이 존재한다.

개행 문자 뒤의 공백은 무시된다.

Takeaway 1.8.3.6. 텍스트의 줄은 개행 문자 뒤의 공백을 포함해서는 안 된다.

C 라이브러리는 파일을 삭제/이름 바꾸기 할 수 있는 인터페이스도 제공한다.

int remove(char const pathname[static 1]);
int rename(char const oldpath[static 1], char const newpath[static 1]);

8.3.4. Formatted output.

printf, fprintf는 서식화된 출력을 제공한다. printf_s, fprintf_s는 스트림/포맷/문자열 인자가 정상 포인터인지를 체크해 준다. 포맷 특정자와 인자가 정확히 대응되는지는 체크하지 않는다.

int printf(char const format[static 1], ...);
int fprintf(FILE* stream, char const format[static 1], ...);
int printf_s(char const format[restrict], ...);
int fprintf_s(FILE *restrict stream, char const format[restrict], ...);

Takeaway 1.8.3.7. printf의 인자는 포맷 특정자와 정확히 대응되어야 한다.

Takeaway 1.8.3.8. 정수형 출력엔 “%d”, “%u”를 사용한다.

Takeaway 1.8.3.9. 비트 패턴 출력엔 “%x”를 사용한다.

Takeaway 1.8.3.10. 부동 소수점 출력엔 “%g”를 사용한다.

Takeaway 1.8.3.11. 포맷 특정자나 한정자가 맞지 않으면 정의되지 않은 행동이 일어난다.

Takeaway 1.8.3.12. 나중에 읽어야 하는 변환에 대해서는 “%+d”, “%#X”, “%a”를 사용한다.

8.3.5. Unformatted text input.

서식화되지 않은 입력은 fgets, fgetc로 이루어진다. gets는 삭제되었다.

int fgetc(FILE* stream);
char* fgets(char s[restrict], int n, FILE* restrict stream);
int getchar(void);

Takeaway 1.8.3.13. gets를 쓰지 말라.

Takeaway 1.8.3.14. fgetc는 int를 리턴하며 특별한 에러 상태 EOF와 모든 올바른 문자를 인코딩한다.

Takeaway 1.8.3.15. 파일의 끝은 읽기 실패 후에만 감지 가능하다.

복수의 텍스트 파일을 stdout에 덤프하는 예제는 다음과 같다.

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

enum {buf_max = 32, };

int main(int argc, char* argv[argc + 1]) {
    int ret = EXIT_FAILURE;
    char buffer[buf_max] = {0};
    for (int i = 1; i < argc; i++) {
        FILE* istream = fopen(argv[i], "r");
        if (istream) {
            while (fgets(buffer, buf_max, istream)) {
                fputs(buffer, stdout);
            }
            fclose(istream);
            ret = EXIT_SUCCESS;
        } else {
            fprintf(stderr, "Could not open %s: ", argv[i]);
            perror(0);
            errno = 0;
        }
    }
    return ret;
}

8.4. String processing and conversion.

C에서 문자열을 다룰 때 가장 중요한 것은 플랫폼별 서로 다른 인코딩에 대처하는 것이다. 이를 위해 C 표준 라이브러리에서는 isalpha, isdigit, toupper, tolower 등 플랫폼 독립적으로 의도한 기능을 하는 문자 관련 함수들을 제공한다.

Takeaway 1.8.4.1. 숫자로 인코딩된 문자의 표현은 실행 문자 집합에 의존한다.

8.5. Time.

time.h 헤더에서는 시간과 시각을 다루는 함수 인터페이스를 제공한다.

time_t time(time_t *t);
double difftime(time_t time1, time_t time0);
time_t mktime(struct tm tm[1]);
size_t strftime(char s[static 1], size_t max, char const format[static 1], struct tm const tm[static 1]);
int timespec_get(struct timespec ts[static 1], int base);
clock_t clock(void);

time은 현재 시각을 time_t 형태의 타임스탬프로 리턴한다. difftime은 time_t로 나타내어진 두 시각의 차를 리턴한다. mktime은 struct tm 안의 시각에 대한 정보를 다시 정규화한 뒤 time_t로 리턴한다. 이 때 time_t의 계산은 일정 기준시간 (구현에 의존하지만 보통 1970.1.1 00:00:00 이후의 에포크 수) 에 따른다. strftime은 시간 정보를 문자열에 포매팅해 기록한다. timespec_get은 timespec 구조체에 담긴 시간 정보를 base에 맞게 재인코딩한다.

8.6. Runtime environment settings.

C 프로그램은 환경 변수 또는 환경 리스트에 접근할 수 있다.

char* getenv(char const name[static 1]);
errno_t getenv_s(size_t* restrict len, char value[restrict], rsize_t maxsize, char const name[restrict]);

이 중 로케일을 설정하거나 불러올 수도 있다.

char* setlocale(int category, char const locale[static 1]);

8.7. Program termination and assertions.

Takeaway 1.8.7.1. 정식 프로그램 종료는 main에서 return해야 한다.

Takeaway 1.8.7.2. 함수에서의 exit은 정식 제어 흐름을 깰 수 있다.

C에서는 exit 외에도 다른 3가지 종료 함수를 제공한다.

_Noreturn void quick_exit(int status);
_Noreturn void _Exit(int status);
_Noreturn void abort(void);

Takeaway 1.8.7.3. 프로그램 종료에서 exit 외에 다른 함수를 쓰면 라이브러리 리소스 청소를 방해하게 된다.

프로그램 종료 시의 핸들러를 세팅하는 것도 가능하다. 이는 함수 인자를 갖는다.

int atexit(void func(void));
int at_quick_exit(void func(void));

Takeaway 1.8.7.4. 런타임 특성을 확정하기 위해 가능한 한 많은 assert를 사용하라.

Takeaway 1.8.7.5. 릴리즈 컴파일에서는 NDEBUG을 사용해 assert를 꺼라.

요점 정리

  • C 라이브러리는 여러 헤더 파일에 인터페이스가 나와 있다.
  • 수학적 함수는 tgmath.h의 타입 제네릭 매크로로 쓰는 것이 최선이다.
  • 입출력 함수는 stdio.h로 인터페이스된다. 텍스트 또는 바이트로 입출력을 할 수 있으며, 텍스트 입출력은 직접 되거나 서식화될 수 있다.
  • 문자열 처리는 문자 분류는 ctype.h, 숫자 변환은 stdlib.h, 문자열 조작은 string.h로 처리한다.
  • time.h는 달력 시간과 물리적 시간을 모두 다룬다.
  • C 표준은 getenv로 환경변수를 알아올 수 있고, locale.h로 로케일을 조사/변경할 수 있다.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중