Proven C BookEnglish GitHub

부록 F — 표준 라이브러리 요람

이 부록은 찾는 글이다. 본문이 「왜」를 다뤘다면 여기는 「무엇을·어떻게」를 한자리에 모은다. C23(N3220)의 부속서 B 를 기준으로 헤더마다 함수·매크로·타입을 빠짐없이 싣는 것을 목표로 한다.

읽는 법은 이렇다.

무엇을 적었나
이름함수 이름. f·l 변형이 있으면 한 칸에 묶었다
표준이 적은 선언. QChar·QVoid한정자를 보존하는 반환을 뜻하는 표준의 표기다(C23) — 넘긴 포인터가 const 면 돌려주는 것도 const
인자와 반환각 매개변수가 무엇이고, 무엇을 돌려주며, 실패를 어떻게 알리는가
하는 일 · ★함정한 줄 요약. ★ 표시는 이 책이 본문에서 다룬 함정이다

표 98.1

세 가지를 미리 밝혀 둔다.

첫째, 부속서 K 함수(_s 로 끝나는 것)도 실었다. 표준에 있으나 선택 사항이고 널리 구현되지 않았다 — 그 사정은 78장에 있다. 요람에서는 ★K 로 표시한다.

둘째, 「함정」 칸이 비어 있으면 「없음」이라고 적는다. 비워 두면 검토하지 않은 것과 구별되지 않기 때문이다.

셋째, 이 부록은 표준을 옮겨 적은 것이 아니다. 선언은 사실이라 그대로 두었지만 설명은 이 책의 문장이며, 값과 동작 가운데 잴 수 있는 것은 재서 적었다.

<string.h> — 문자열과 기억

본문: 65장(정독), 42장(문자열의 정체), 43장(안전한 입력).

이 헤더는 크기를 받지 않는 함수받는 함수가 섞여 있다. 그 갈림이 사고의 갈림이기도 하다.

기억 블록 다루기 (mem*)

이름인자와 반환하는 일 · ★함정
memcpyvoid *memcpy(void *restrict s1, const void *restrict s2, size_t n)s1 목적지, s2 출처, n 바이트 수. 반환은 s1n 바이트를 옮긴다. ★ 두 영역이 겹치면 계약 밖이다restrict 가 그 약속이다. 겹치면 memmove
memmovevoid *memmove(void *s1, const void *s2, size_t n)같음. 반환은 s1겹쳐도 옳게 옮긴다. 함정 없음 — 겹칠지 모르면 이것을 쓴다
memcmpint memcmp(const void *s1, const void *s2, size_t n)두 영역과 길이. 반환은 음수·0·양수바이트를 사전순으로 견준다. ★ 구조체를 통째로 견주면 안 된다 — 패딩이 값에 낀다(47장)
memchrQVoid *memchr(QVoid *s, int c, size_t n)cunsigned char 로 변환되어 찾는 값. 못 찾으면 널앞에서부터 n 바이트 안에서 찾는다. 문자열과 달리 NUL 에서 멈추지 않는다
memsetvoid *memset(void *s, int c, size_t n)cunsigned char 로 변환. 반환은 sn 바이트를 같은 값으로 채운다. ★ 포인터를 널로 만드는 수단이 아니다 — 비트 0 과 널 표현은 별개다(36장)
memset_explicitvoid *memset_explicit(void *s, int c, size_t n)memset 과 같다C23. 최적화가 지우지 못하는 지우기 — 비밀번호를 지운 뒤 곧 해제할 때 쓴다
memccpyvoid *memccpy(void *restrict s1, const void *restrict s2, int c, size_t n)c 를 만나면 거기까지 복사하고 그 다음 자리를 돌려준다. 못 만나면 널C23 에 들어왔다(오래된 POSIX 함수). 「구분자까지 복사」를 한 번에 한다
memcpy_serrno_t memcpy_s(void *restrict s1, rsize_t s1max, const void *restrict s2, rsize_t n)목적지 크기 s1max 를 함께 받는다. 성공은 0★K 부속서 K. 넘치면 목적지를 0 으로 채우고 오류를 낸다. 널리 구현되지 않았다(78장)
memmove_serrno_t memmove_s(void *s1, rsize_t s1max, const void *s2, rsize_t n)같음★K 같은 사정
memset_serrno_t memset_s(void *s, rsize_t smax, int c, rsize_t n)같음★K 같은 사정. memset_explicit 이 C23 의 답이다

표 98.2

복사와 이어 붙이기 (strcpy·strcat 계열)

이름인자와 반환하는 일 · ★함정
strcpychar *strcpy(char *restrict s1, const char *restrict s2)반환은 s1NUL 까지 통째로 옮긴다. ★ 목적지 크기를 모른다 — 넘침의 고전
strncpychar *strncpy(char *restrict s1, const char *restrict s2, size_t n)n쓸 바이트 수이지 목적지 크기가 아니다★★ 이름과 달리 안전 함수가 아니다. 딱 맞으면 NUL 을 붙이지 않고, 짧으면 남은 자리를 전부 0 으로 채운다(느리다). 65장
strcatchar *strcat(char *restrict s1, const char *restrict s2)반환은 s1뒤에 이어 붙인다. ★ 목적지 크기를 모르고, 매번 끝을 다시 찾아 이어 붙이므로 반복하면 제곱이 된다
strncatchar *strncat(char *restrict s1, const char *restrict s2, size_t n)n더 붙일 최대 바이트 수. NUL 은 그 위에 하나 더 쓴다★ 그래서 필요한 그릇은 n + 1 이다 — strncpy 와 셈법이 다르다
strdupchar *strdup(const char *s)실패하면 널C23 에 들어왔다. free 해야 한다 — 소유가 넘어온다(89장)
strndupchar *strndup(const char *s, size_t n)최대 n 바이트까지. 실패하면 널C23. 끝을 못 찾아도 n 에서 멈춘다
strcpy_serrno_t strcpy_s(char *restrict s1, rsize_t s1max, const char *restrict s2)성공은 0★K 78장
strncpy_serrno_t strncpy_s(char *restrict s1, rsize_t s1max, const char *restrict s2, rsize_t n)성공은 0★K 78장
strcat_serrno_t strcat_s(char *restrict s1, rsize_t s1max, const char *restrict s2)성공은 0★K 78장
strncat_serrno_t strncat_s(char *restrict s1, rsize_t s1max, const char *restrict s2, rsize_t n)성공은 0★K 78장

표 98.3

견주기와 길이

이름인자와 반환하는 일 · ★함정
strlensize_t strlen(const char *s)NUL 을 세지 않은 바이트 수글자 수가 아니라 바이트 수다(42·72장). 그리고 O(n) 이므로 루프 조건에 두지 않는다(41장)
strcmpint strcmp(const char *s1, const char *s2)음수·0·양수「사전순」이 아니라 unsigned char 값 순이다. 사람이 쓰는 정렬은 strcoll
strncmpint strncmp(const char *s1, const char *s2, size_t n)같음n 바이트만 견준다. 접두어 검사에 쓴다
strcollint strcoll(const char *s1, const char *s2)같음로케일이 정한 순서로 견준다(69장). 느리다 — 여러 번 견줄 것이면 strxfrm
strxfrmsize_t strxfrm(char *restrict s1, const char *restrict s2, size_t n)필요한 길이를 돌려준다(NUL 제외). n 이상이면 s1 의 내용은 미정로케일 비교를 «키»로 굳힌다. 키끼리는 strcmp 로 견줄 수 있다
strnlen_ssize_t strnlen_s(const char *s, size_t maxsize)최대 maxsize. s 가 널이면 0★K 그러나 이것만은 널리 쓸 만하다 — 끝이 없을지 모르는 버퍼의 길이를 잰다

표 98.4

찾기와 쪼개기

이름인자와 반환하는 일 · ★함정
strchrQChar *strchr(QChar *s, int c)못 찾으면 널. c'\0' 이면 끝의 NUL 을 가리킨다앞에서부터 한 바이트를 찾는다. ★ 멀티바이트 인코딩에서 글자 속을 짚을 수 있다(72장)
strrchrQChar *strrchr(QChar *s, int c)같음뒤에서부터 찾는다. 경로에서 마지막 구분자를 찾는 데 쓰는데 — ★ 위와 같은 위험
strstrQChar *strstr(QChar *s1, const char *s2)못 찾으면 널. s2 가 빈 문자열이면 s1부분 문자열을 찾는다. 표준은 알고리즘을 정하지 않는다 — 최악 O(nm) 일 수 있다
strspnsize_t strspn(const char *s1, const char *s2)길이를 돌려준다s2 의 글자들로만 이루어진 앞부분의 길이. 건너뛰기에 쓴다
strcspnsize_t strcspn(const char *s1, const char *s2)길이를 돌려준다반대 — s2 의 글자를 만나기 전까지의 길이
strpbrkQChar *strpbrk(QChar *s1, const char *s2)못 찾으면 널s2 의 글자 아무거나 처음 나오는 자리
strtokchar *strtok(char *restrict s1, const char *restrict s2)첫 호출에 문자열, 다음부터 널. 더 없으면 널★★ 원본을 부순다(구분자 자리에 NUL 을 쓴다). 그리고 상태를 함수 안에 숨긴다 — 재진입 불가, 스레드 안전하지 않다. 65장
strtok_schar *strtok_s(char *restrict s1, rsize_t *restrict s1max, const char *restrict s2, char **restrict ptr)상태를 ptr 로 밖에 둔다★K 상태를 밖으로 뺀 것은 옳은 방향이다. POSIX 의 strtok_r 이 같은 생각

표 98.5

오류 문자열

이름인자와 반환하는 일 · ★함정
strerrorchar *strerror(int errnum)오류 번호에 대응하는 문자열정적 버퍼를 돌려줄 수 있다 — 다음 호출이 덮어쓸 수 있고 스레드 안전이 보장되지 않는다(75장)
strerrorlen_ssize_t strerrorlen_s(errno_t errnum)필요한 길이★K strerror_s 와 짝을 이룬다

표 98.6

매크로·타입무엇인가주의
NULL널 포인터 상수C23 부터는 nullptr 가 있다(36·82장)
size_t크기·개수의 부호 없는 타입<stddef.h> 가 본가다(35장)
rsize_t★K 부속서 K 의 크기 타입RSIZE_MAX 로 상한을 검사한다
errno_t★K 부속서 K 의 오류 타입사실상 int

표 98.7

<ctype.h> — 한 바이트의 갈래

본문: 67장(정독), 9장(문자 집합의 역사).

열네 함수가 전부 같은 꼴이다 — int 를 받아 int 를 돌려준다. 그 한 줄에 ★ 이 헤더 전체를 지배하는 함정이 있다.

모든 함수에 공통내용
인자unsigned char 로 표현할 수 있는 값 또는 EOF. ★ 그 밖의 값은 계약 밖이다
가장 흔한 사고char 를 그대로 넘기는 것. 부호 있는 char 인 기계에서 128 이상의 바이트가 음수가 되어 계약을 벗어난다. 반드시 isalpha((unsigned char)c)
반환판정 함수는 «참이면 0 이 아닌 값», 거짓이면 0. 1 이라고 가정하지 않는다
로케일isdigit·isxdigit 을 뺀 나머지는 LC_CTYPE 이 바꿀 수 있다(68장)

표 98.8

이름무엇이 참인가비고 · ★함정
isalnum글자 또는 숫자isalpha 이거나 isdigit
isalpha글자「A~Z, a~z」가 아니다 — 로케일과 문자 집합이 정한다(9장의 EBCDIC)
isblank칸 띄우개 — 공백과 수평 탭C99. 「줄 안의 빈칸」을 뜻한다
iscntrl제어 문자화면에 그려지지 않는 것
isdigit0~9로케일과 무관하다 — 표준이 이 열 글자로 못박았다
isgraph공백을 뺀, 그려지는 글자isprint 에서 공백을 뺀 것
islower소문자로케일이 정한다
isprint공백을 포함해 그려지는 글자
ispunct글자도 숫자도 공백도 아닌, 그려지는 글자
isspace공백류 — 공백·\\n·\\t·\\v·\\f·\\r입력을 건너뛸 때 쓴다
isupper대문자로케일이 정한다
isxdigit16진 숫자★ 로케일과 무관하다
tolower대문자면 대응하는 소문자, 아니면 그대로한 글자 대 한 글자다. 터키어 I, 독일어 ß 처럼 그 가정이 깨지는 언어가 있다(72장)
toupper소문자면 대응하는 대문자, 아니면 그대로★ 같음

표 98.9

<stdckdint.h> — 넘침을 묻는 산술

본문: 81장(정독), 27장(정수의 유한함), 52장(정의되지 않은 동작).

C23 이 들인 헤더다. 셋 다 타입 제네릭 매크로이지 함수가 아니다 — 그래서 꼴에 type1·type2 라고 적혀 있다.

이름인자와 반환하는 일 · ★함정
ckd_addbool ckd_add(type1 *result, type2 a, type3 b)result 에 결과를 쓴다. 넘쳤으면 참, 아니면 거짓을 돌려준다★ 반환값의 방향에 주의 — 참이 「성공」이 아니라 「넘쳤다」이다
ckd_subbool ckd_sub(type1 *result, type2 a, type3 b)같음뺄셈. 부호 없는 타입에서 0 아래로 내려가는 것도 잡는다
ckd_mulbool ckd_mul(type1 *result, type2 a, type3 b)같음곱셈. ★ 할당 크기 계산에 가장 쓸모 있다 — n * sizeof *p 의 넘침이 고전적인 보안 사고다

표 98.10

공통 규약내용비고
결과의 정확성넘치지 않았다면 수학적으로 옳은 값「감아 돈 값」이 아니다
넘쳤을 때result 에는 감아 돈 값이 들어간다미정의 동작이 아니다 — 값이 정해져 있다
피연산자bool 과 비트 정밀 정수를 뺀 정수 타입char 도 허용된다
매크로 검사__STDC_VERSION_STDCKDINT_H__있는지 보고 갈라 쓴다

표 98.11