<string.h>의 모든 함수는 널 종료 문자에서 멈추는, char 배열 위의 작은 반복문입니다. 그것이 분명해지면 이 라이브러리는 알 수 없는 이름의 목록이 아니라 여러분이 직접 썼을 몇 개의 반복문처럼 보이기 시작합니다. 이 페이지는 실제 작업 대부분을 해내는 함수들과, 그것들이 버퍼 끝을 넘어 쓰지 못하게 막는 크기 규칙을 다룹니다.
여기의 모든 것은 인클루드 하나가 필요합니다.
#include <string.h>
strlen: 문자가 몇 개인가
strlen은 종료 문자 직전까지의 바이트를 셉니다. size_t를 반환하므로 %zu로 출력하세요.
strlen은 6, sizeof는 32입니다. 앞의 것은 텍스트, 뒤의 것은 저장 공간입니다. 이 구분이 왜 그렇게 중요한지는 문자열을 보세요.
strlen은 호출할 때마다 문자열 전체를 훑으므로, 바뀌지 않는 같은 문자열에 대해 반복문 조건에 넣지 마세요.
/* 매 반복마다 문자열 전체를 다시 센다 */
for (size_t i = 0; i < strlen(s); i++) { ... }
/* 한 번만 센다 */
size_t n = strlen(s);
for (size_t i = 0; i < n; i++) { ... }
strcpy와 strncpy: 복사
strcpy(dst, src)는 문자들과 종료 문자를 함께 복사합니다. dst가 얼마나 큰지 전혀 모르므로, 호출자가 dst에 적어도 strlen(src) + 1 바이트가 있음을 보장해야 합니다.
strncpy는 최대 개수를 받지만 유명한 함정이 있습니다. 원본이 n 바이트 이상이면 정확히 n개의 문자를 복사하고 종료 문자는 넣지 않습니다. 항상 직접 종료시키세요.
sizeof dst - 1에 명시적인 종료 문자를 더한 것이 외워 둘 패턴입니다. (strncpy는 짧은 원본을 n 바이트까지 0으로 채우기도 하는데, 큰 버퍼에서는 낭비입니다. 안전이 아니라 고정폭 레코드를 위해 설계된 함수입니다.)
strcat과 strncat: 잇기
strcat(dst, src)는 dst에 이미 들어 있는 것의 끝에 src를 덧붙입니다. 목적지는 두 문자열과 종료 문자 하나를 담을 수 있어야 하고, 이미 유효한 문자열을 담고 있어야 합니다. 초기화되지 않은 메모리에 덧붙이는 것은 미정의 동작입니다.
char path[64];가 아니라 char path[64] = "/home/ada";인 점을 보세요. 첫 strcat에 대해 이것을 유효한 문자열로 만들어 주는 것이 바로 그 초기화자입니다.
strncat(dst, src, n)은 최대 n개의 문자를 덧붙이고 항상 종료 문자를 넣으므로, n은 전체 버퍼 크기가 아니라 남은 공간입니다.
strncat(dst, src, sizeof dst - strlen(dst) - 1);
strcat을 반복 호출하면 매번 목적지를 다시 훑어 끝을 찾습니다. 그런 식으로 반복문에서 긴 문자열을 만드는 것은 제곱 시간입니다. 규모가 있는 일이라면 자기 쓰기 위치를 직접 추적하거나 문자열 변환에서 다루는 snprintf를 쓰세요.
strcmp: 비교
==는 주소를 비교하므로 같은 텍스트를 담은 서로 다른 배열 둘에 대해 거짓입니다. strcmp는 문자를 비교하고 차이의 부호를 반환합니다.
규칙 셋.
- 같은지 보려면
strcmp(a, b) == 0을 검사하세요.if (strcmp(a, b))라고 쓰면 "다르면"을 뜻하는데, 거의 모든 사람에게 거꾸로 읽힙니다. - 정의된 것은 부호뿐입니다. 결과를
1이나-1과 비교하지 마세요. - 순서는 바이트 값 기준이므로 ASCII에서
"Zebra"가"apple"보다 앞에 정렬됩니다. 대소문자 무시 비교를 하려면 양쪽 사본을 먼저 소문자로 만드세요. 흔히 보이는strcasecmp는 POSIX 확장이지 표준 C가 아닙니다.
strncmp(a, b, n)은 앞의 n개 문자만 비교하므로, 접두사를 검사하는 깔끔한 방법입니다.
strchr과 strstr: 찾기
strchr(s, ch)은 문자가 처음 나타나는 자리를, strrchr은 마지막 자리를 찾습니다. strstr(haystack, needle)은 부분 문자열을 찾습니다. 셋 다 원본 문자열 안을 가리키는 포인터를 반환하고, 일치하는 것이 없으면 NULL을 반환합니다.
반환된 포인터에서 시작 주소를 빼면 인덱스가 나옵니다. 타입은 ptrdiff_t이고 %td로 출력합니다. 결과가 원본 배열 안을 가리키므로 at + 1은 복사 없이 문자열의 나머지가 됩니다. 아주 흔한 C 관용구입니다.
역참조하기 전에 항상 NULL을 확인하세요. @가 없는 문자열에 strchr(email, '@') + 1을 하면 NULL로부터 주소를 계산하게 되고, 프로그램은 더 이상 정의되지 않습니다.
문자열 나누기
split 함수는 없지만, strchr에 종료 문자를 더하면 제자리에서 그 일을 해냅니다. 다음 예제는 이메일을 사용자와 도메인으로 자릅니다.
memcpy는 종료 문자에 신경 쓰지 않고 정확한 바이트 수를 복사하는데, 길이를 이미 알고 있을 때 바라는 바로 그것입니다. 종료 문자는 그다음에 손으로 씁니다.
실전 예제: 이름 정규화하기
지금까지의 내용을 모아 봅시다. 공백 제거, 비교, 복사, 잇기입니다.
모든 쓰기는 목적지 자신의 sizeof로 제한되어 있고, 모든 strncpy 뒤에는 명시적인 종료 문자가 따라옵니다. 그 규율을 일관되게 지키는 것이 C 문자열 코드를 안전하게 만듭니다.
기억할 크기 규칙
strcpy는 목적지에strlen(src) + 1바이트가 비어 있어야 합니다.strcat은strlen(dst) + strlen(src) + 1이 필요합니다.strncpy뒤에는 직접dst[n - 1] = '\0'을 쓰세요.strncat의 개수는 남은 공간입니다.sizeof dst - strlen(dst) - 1입니다.- 결과를 쓰기 전에
strchr과strstr의NULL여부를 확인하세요. sizeof dst는dst가 진짜 배열일 때만 쓰세요.char *dst를 받는 함수 안에서sizeof는 포인터의 크기를 줍니다. 버퍼 길이를 별도 매개변수로 넘기세요.
자주 묻는 질문
C에서 strcmp는 무엇을 반환하나요?
두 문자열이 같으면 0, 첫 번째가 앞에 정렬되면 음수, 뒤에 정렬되면 양수를 반환합니다. 의미가 있는 것은 부호뿐이며, -1이나 1을 반환한다고 가정하지 마세요. 원하는 검사는 거의 항상 if (strcmp(a, b) == 0)입니다.
C에서 왜 ==로 문자열을 비교할 수 없나요?
a == b는 문자가 아니라 두 주소를 비교합니다. "cat"을 담은 별개의 배열 둘은 서로 다른 주소에 있으므로, 텍스트가 같아도 비교는 거짓입니다. <string.h>의 strcmp(a, b) == 0을 쓰세요.
strcpy와 strncpy의 차이는 무엇인가요?
strcpy는 목적지가 얼마나 큰지 전혀 모른 채 원본의 '\0'까지 복사합니다. strncpy는 최대 n바이트에서 멈추지만, 원본이 그만큼 길면 종료 문자를 복사하지 않으므로 dst[n - 1] = '\0';을 직접 써야 합니다. 어느 쪽도 자동으로 안전하지 않고, 크기 관리는 여러분 몫입니다.
C에서 부분 문자열은 어떻게 찾나요?
strstr(haystack, needle)이 첫 등장 위치의 포인터를 반환하고, 없으면 NULL을 반환합니다. 원본 문자열 안을 가리키는 포인터이므로 found - haystack이 인덱스가 됩니다. 문자 하나를 찾을 때는 strchr을 쓰세요.