부록 F — 표준 라이브러리 요람
이 부록은 찾는 글이다. 본문이 「왜」를 다뤘다면 여기는 「무엇을·어떻게」를 한자리에 모은다. C23(N3220)의 부속서 B 를 기준으로 헤더마다 함수·매크로·타입을 빠짐없이 싣는 것을 목표로 한다.
읽는 법은 이렇다.
| 열 | 무엇을 적었나 |
|---|---|
| 이름 | 함수 이름. f·l 변형이 있으면 한 칸에 묶었다 |
| 꼴 | 표준이 적은 선언. QChar·QVoid 는 한정자를 보존하는 반환을 뜻하는 표준의 표기다(C23) — 넘긴 포인터가 const 면 돌려주는 것도 const 다 |
| 인자와 반환 | 각 매개변수가 무엇이고, 무엇을 돌려주며, 실패를 어떻게 알리는가 |
| 하는 일 · ★함정 | 한 줄 요약. ★ 표시는 이 책이 본문에서 다룬 함정이다 |
표 98.1
세 가지를 미리 밝혀 둔다.
첫째, 부속서 K 함수(_s 로 끝나는 것)도 실었다. 표준에 있으나 선택 사항이고 널리 구현되지 않았다 — 그 사정은 78장에 있다. 요람에서는 ★K 로 표시한다.
둘째, 「함정」 칸이 비어 있으면 「없음」이라고 적는다. 비워 두면 검토하지 않은 것과 구별되지 않기 때문이다.
셋째, 이 부록은 표준을 옮겨 적은 것이 아니다. 선언은 사실이라 그대로 두었지만 설명은 이 책의 문장이며, 값과 동작 가운데 잴 수 있는 것은 재서 적었다.
<string.h> — 문자열과 기억
본문: 65장(정독), 42장(문자열의 정체), 43장(안전한 입력).
이 헤더는 크기를 받지 않는 함수와 받는 함수가 섞여 있다. 그 갈림이 사고의 갈림이기도 하다.
기억 블록 다루기 (mem*)
| 이름 | 꼴 | 인자와 반환 | 하는 일 · ★함정 |
|---|---|---|---|
memcpy | void *memcpy(void *restrict s1, const void *restrict s2, size_t n) | s1 목적지, s2 출처, n 바이트 수. 반환은 s1 | n 바이트를 옮긴다. ★ 두 영역이 겹치면 계약 밖이다 — restrict 가 그 약속이다. 겹치면 memmove |
memmove | void *memmove(void *s1, const void *s2, size_t n) | 같음. 반환은 s1 | 겹쳐도 옳게 옮긴다. 함정 없음 — 겹칠지 모르면 이것을 쓴다 |
memcmp | int memcmp(const void *s1, const void *s2, size_t n) | 두 영역과 길이. 반환은 음수·0·양수 | 바이트를 사전순으로 견준다. ★ 구조체를 통째로 견주면 안 된다 — 패딩이 값에 낀다(47장) |
memchr | QVoid *memchr(QVoid *s, int c, size_t n) | c 는 unsigned char 로 변환되어 찾는 값. 못 찾으면 널 | 앞에서부터 n 바이트 안에서 찾는다. 문자열과 달리 NUL 에서 멈추지 않는다 |
memset | void *memset(void *s, int c, size_t n) | c 는 unsigned char 로 변환. 반환은 s | n 바이트를 같은 값으로 채운다. ★ 포인터를 널로 만드는 수단이 아니다 — 비트 0 과 널 표현은 별개다(36장) |
memset_explicit | void *memset_explicit(void *s, int c, size_t n) | memset 과 같다 | C23. 최적화가 지우지 못하는 지우기 — 비밀번호를 지운 뒤 곧 해제할 때 쓴다 |
memccpy | void *memccpy(void *restrict s1, const void *restrict s2, int c, size_t n) | c 를 만나면 거기까지 복사하고 그 다음 자리를 돌려준다. 못 만나면 널 | C23 에 들어왔다(오래된 POSIX 함수). 「구분자까지 복사」를 한 번에 한다 |
memcpy_s | errno_t memcpy_s(void *restrict s1, rsize_t s1max, const void *restrict s2, rsize_t n) | 목적지 크기 s1max 를 함께 받는다. 성공은 0 | ★K 부속서 K. 넘치면 목적지를 0 으로 채우고 오류를 낸다. 널리 구현되지 않았다(78장) |
memmove_s | errno_t memmove_s(void *s1, rsize_t s1max, const void *s2, rsize_t n) | 같음 | ★K 같은 사정 |
memset_s | errno_t memset_s(void *s, rsize_t smax, int c, rsize_t n) | 같음 | ★K 같은 사정. memset_explicit 이 C23 의 답이다 |
표 98.2
복사와 이어 붙이기 (strcpy·strcat 계열)
| 이름 | 꼴 | 인자와 반환 | 하는 일 · ★함정 |
|---|---|---|---|
strcpy | char *strcpy(char *restrict s1, const char *restrict s2) | 반환은 s1 | NUL 까지 통째로 옮긴다. ★ 목적지 크기를 모른다 — 넘침의 고전 |
strncpy | char *strncpy(char *restrict s1, const char *restrict s2, size_t n) | n 은 쓸 바이트 수이지 목적지 크기가 아니다 | ★★ 이름과 달리 안전 함수가 아니다. 딱 맞으면 NUL 을 붙이지 않고, 짧으면 남은 자리를 전부 0 으로 채운다(느리다). 65장 |
strcat | char *strcat(char *restrict s1, const char *restrict s2) | 반환은 s1 | 뒤에 이어 붙인다. ★ 목적지 크기를 모르고, 매번 끝을 다시 찾아 이어 붙이므로 반복하면 제곱이 된다 |
strncat | char *strncat(char *restrict s1, const char *restrict s2, size_t n) | n 은 더 붙일 최대 바이트 수. NUL 은 그 위에 하나 더 쓴다 | ★ 그래서 필요한 그릇은 n + 1 이다 — strncpy 와 셈법이 다르다 |
strdup | char *strdup(const char *s) | 실패하면 널 | C23 에 들어왔다. free 해야 한다 — 소유가 넘어온다(89장) |
strndup | char *strndup(const char *s, size_t n) | 최대 n 바이트까지. 실패하면 널 | C23. 끝을 못 찾아도 n 에서 멈춘다 |
strcpy_s | errno_t strcpy_s(char *restrict s1, rsize_t s1max, const char *restrict s2) | 성공은 0 | ★K 78장 |
strncpy_s | errno_t strncpy_s(char *restrict s1, rsize_t s1max, const char *restrict s2, rsize_t n) | 성공은 0 | ★K 78장 |
strcat_s | errno_t strcat_s(char *restrict s1, rsize_t s1max, const char *restrict s2) | 성공은 0 | ★K 78장 |
strncat_s | errno_t strncat_s(char *restrict s1, rsize_t s1max, const char *restrict s2, rsize_t n) | 성공은 0 | ★K 78장 |
표 98.3
견주기와 길이
| 이름 | 꼴 | 인자와 반환 | 하는 일 · ★함정 |
|---|---|---|---|
strlen | size_t strlen(const char *s) | NUL 을 세지 않은 바이트 수 | ★ 글자 수가 아니라 바이트 수다(42·72장). 그리고 O(n) 이므로 루프 조건에 두지 않는다(41장) |
strcmp | int strcmp(const char *s1, const char *s2) | 음수·0·양수 | ★ 「사전순」이 아니라 unsigned char 값 순이다. 사람이 쓰는 정렬은 strcoll |
strncmp | int strncmp(const char *s1, const char *s2, size_t n) | 같음 | 앞 n 바이트만 견준다. 접두어 검사에 쓴다 |
strcoll | int strcoll(const char *s1, const char *s2) | 같음 | 로케일이 정한 순서로 견준다(69장). 느리다 — 여러 번 견줄 것이면 strxfrm |
strxfrm | size_t strxfrm(char *restrict s1, const char *restrict s2, size_t n) | 필요한 길이를 돌려준다(NUL 제외). n 이상이면 s1 의 내용은 미정 | 로케일 비교를 «키»로 굳힌다. 키끼리는 strcmp 로 견줄 수 있다 |
strnlen_s | size_t strnlen_s(const char *s, size_t maxsize) | 최대 maxsize. s 가 널이면 0 | ★K 그러나 이것만은 널리 쓸 만하다 — 끝이 없을지 모르는 버퍼의 길이를 잰다 |
표 98.4
찾기와 쪼개기
| 이름 | 꼴 | 인자와 반환 | 하는 일 · ★함정 |
|---|---|---|---|
strchr | QChar *strchr(QChar *s, int c) | 못 찾으면 널. c 가 '\0' 이면 끝의 NUL 을 가리킨다 | 앞에서부터 한 바이트를 찾는다. ★ 멀티바이트 인코딩에서 글자 속을 짚을 수 있다(72장) |
strrchr | QChar *strrchr(QChar *s, int c) | 같음 | 뒤에서부터 찾는다. 경로에서 마지막 구분자를 찾는 데 쓰는데 — ★ 위와 같은 위험 |
strstr | QChar *strstr(QChar *s1, const char *s2) | 못 찾으면 널. s2 가 빈 문자열이면 s1 | 부분 문자열을 찾는다. 표준은 알고리즘을 정하지 않는다 — 최악 O(nm) 일 수 있다 |
strspn | size_t strspn(const char *s1, const char *s2) | 길이를 돌려준다 | s2 의 글자들로만 이루어진 앞부분의 길이. 건너뛰기에 쓴다 |
strcspn | size_t strcspn(const char *s1, const char *s2) | 길이를 돌려준다 | 반대 — s2 의 글자를 만나기 전까지의 길이 |
strpbrk | QChar *strpbrk(QChar *s1, const char *s2) | 못 찾으면 널 | s2 의 글자 아무거나 처음 나오는 자리 |
strtok | char *strtok(char *restrict s1, const char *restrict s2) | 첫 호출에 문자열, 다음부터 널. 더 없으면 널 | ★★ 원본을 부순다(구분자 자리에 NUL 을 쓴다). 그리고 상태를 함수 안에 숨긴다 — 재진입 불가, 스레드 안전하지 않다. 65장 |
strtok_s | char *strtok_s(char *restrict s1, rsize_t *restrict s1max, const char *restrict s2, char **restrict ptr) | 상태를 ptr 로 밖에 둔다 | ★K 상태를 밖으로 뺀 것은 옳은 방향이다. POSIX 의 strtok_r 이 같은 생각 |
표 98.5
오류 문자열
| 이름 | 꼴 | 인자와 반환 | 하는 일 · ★함정 |
|---|---|---|---|
strerror | char *strerror(int errnum) | 오류 번호에 대응하는 문자열 | ★ 정적 버퍼를 돌려줄 수 있다 — 다음 호출이 덮어쓸 수 있고 스레드 안전이 보장되지 않는다(75장) |
strerrorlen_s | size_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 | 제어 문자 | 화면에 그려지지 않는 것 |
isdigit | 0~9 | ★ 로케일과 무관하다 — 표준이 이 열 글자로 못박았다 |
isgraph | 공백을 뺀, 그려지는 글자 | isprint 에서 공백을 뺀 것 |
islower | 소문자 | 로케일이 정한다 |
isprint | 공백을 포함해 그려지는 글자 | |
ispunct | 글자도 숫자도 공백도 아닌, 그려지는 글자 | |
isspace | 공백류 — 공백·\\n·\\t·\\v·\\f·\\r | 입력을 건너뛸 때 쓴다 |
isupper | 대문자 | 로케일이 정한다 |
isxdigit | 16진 숫자 | ★ 로케일과 무관하다 |
tolower | 대문자면 대응하는 소문자, 아니면 그대로 | ★ 한 글자 대 한 글자다. 터키어 I, 독일어 ß 처럼 그 가정이 깨지는 언어가 있다(72장) |
toupper | 소문자면 대응하는 대문자, 아니면 그대로 | ★ 같음 |
표 98.9
<stdckdint.h> — 넘침을 묻는 산술
본문: 81장(정독), 27장(정수의 유한함), 52장(정의되지 않은 동작).
C23 이 들인 헤더다. 셋 다 타입 제네릭 매크로이지 함수가 아니다 — 그래서 꼴에 type1·type2 라고 적혀 있다.
| 이름 | 꼴 | 인자와 반환 | 하는 일 · ★함정 |
|---|---|---|---|
ckd_add | bool ckd_add(type1 *result, type2 a, type3 b) | result 에 결과를 쓴다. 넘쳤으면 참, 아니면 거짓을 돌려준다 | ★ 반환값의 방향에 주의 — 참이 「성공」이 아니라 「넘쳤다」이다 |
ckd_sub | bool ckd_sub(type1 *result, type2 a, type3 b) | 같음 | 뺄셈. 부호 없는 타입에서 0 아래로 내려가는 것도 잡는다 |
ckd_mul | bool ckd_mul(type1 *result, type2 a, type3 b) | 같음 | 곱셈. ★ 할당 크기 계산에 가장 쓸모 있다 — n * sizeof *p 의 넘침이 고전적인 보안 사고다 |
표 98.10
| 공통 규약 | 내용 | 비고 |
|---|---|---|
| 결과의 정확성 | 넘치지 않았다면 수학적으로 옳은 값 | 「감아 돈 값」이 아니다 |
| 넘쳤을 때 | result 에는 감아 돈 값이 들어간다 | 미정의 동작이 아니다 — 값이 정해져 있다 |
| 피연산자 | bool 과 비트 정밀 정수를 뺀 정수 타입 | char 도 허용된다 |
| 매크로 검사 | __STDC_VERSION_STDCKDINT_H__ | 있는지 보고 갈라 쓴다 |
표 98.11