Menu

C 언어 문자열: char 배열, 널 종료 문자, 예제

C에는 문자열 타입이 없습니다. 문자열은 '\0' 바이트로 끝나는 char 배열입니다. 이 페이지는 그 모델을 세운 뒤 리터럴, 출력, 순회, 그리고 =로 문자열을 복사할 수 없는 이유를 다룹니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

대부분의 언어는 자기 길이를 아는 string 타입을 건네줍니다. C는 그러지 않습니다. C에서 문자열은 규칙 하나가 붙은 char 배열일 뿐입니다. 텍스트 뒤에 값이 0인 바이트가 따라오고, 이를 '\0'으로 쓰며 널 종료 문자라고 부릅니다. C 문자열에 대한 나머지 모든 것이 이 한 가지 관례에서 따라 나옵니다.

문자열은 char 배열 더하기 '\0'

리터럴로 char 배열을 초기화해 문자열을 선언합니다.

배열은 셋이 아니라 네 바이트로 생겼습니다.

name:  +-----+-----+-----+------+
       | 'A' | 'd' | 'a' | '\0' |
       +-----+-----+-----+------+
index:    0     1     2     3

sizeof(name)4입니다. 컴파일러가 문자를 세고 종료 문자를 위해 하나를 더해 배열 크기를 정해 줬습니다. 이 여분의 바이트 때문에 char name[3] = "Ada";는 터지기를 기다리는 버그입니다. 텍스트는 들어가지만 종료 문자가 들어가지 못하므로, 그 뒤의 어떤 코드도 문자열이 어디서 끝나는지 알 수 없습니다.

나중에 내용을 바꿀 여유를 원한다면 크기를 직접 주고 여백을 남기세요.

char name[32] = "Ada";   /* 문자 3개, 종료 문자, 여분 28바이트 */

쓰이지 않은 바이트는 초기화자가 0으로 채우므로 무해합니다.

종료 문자가 중요한 이유

어떤 C 함수도 길이를 받지 않습니다. printf("%s", s)는 주소 하나를 받아 '\0'을 만날 때까지 앞으로 나아가며 바이트를 출력합니다. 그래서 종료 문자가 없는 char 배열은 문자열이 아니고, 그것을 문자열로 다루면 뒤이어 있는 아무 메모리나 읽게 됩니다.

외워 둘 규칙은 이렇습니다. sizeof는 저장 공간을, strlen은 텍스트를 알려 줍니다. char buf[32] = "Ada"에서 sizeof buf32이고 strlen(buf)3입니다. 둘은 다른 질문에 답하며, 헷갈리는 것이 가장 흔한 C 버그 중 하나입니다.

문자열 리터럴과 char 배열

다음 두 줄은 비슷해 보이지만 아주 다르게 동작합니다.

char  a[] = "hello";   /* 리터럴로 초기화된, 내가 소유한 배열 */
char *p   = "hello";   /* 리터럴 자체를 겨눈 포인터 */

a는 텍스트의 개인 사본을 담은 6바이트 배열입니다. 수정할 수 있습니다. p는 프로그램의 읽기 전용 문자열 데이터를 가리킵니다. 리터럴은 공유되고, p를 통해 쓰는 것은 미정의 동작이며 보통 컴파일 타임이 아니라 실행 중에 죽습니다.

포인터가 리터럴을 겨눌 때는 언제나 const char *라고 쓰세요. 그러면 컴파일러가 쓰기를 실행 중 크래시로 두지 않고 컴파일 타임에 거부합니다. 둘 사이의 선택은 사실 포인터와 배열에 관한 질문이고, 포인터와 배열에서 다룹니다.

알아둘 차이가 하나 더 있습니다. sizeof a6이지만, sizeof p는 텍스트 길이와 무관하게 포인터의 크기(대부분의 시스템에서 8)입니다.

문자열 출력과 입력

%s는 문자열을, %c는 문자 하나를 출력합니다.

입력에는 gets를 아예 쓰지 마세요. 안전하게 쓸 방법이 없어서 언어에서 제거되었습니다. 버퍼 크기를 받아 넘치지 않는 fgets를 쓰세요.

fgets는 여러분이 누른 줄바꿈을 그대로 남기는데, 문자열 안에 그것이 있기를 바라는 경우는 거의 없습니다. strcspn 줄은 첫 '\n'을 찾아 종료 문자로 덮어씁니다. 간결하고 표준적인 제거 방법입니다.

문자열을 한 글자씩 훑기

끝이 세어지지 않고 표시되어 있으므로, 관용적인 반복문은 문자 자체를 검사합니다.

조건 text[i] != '\0'은 종종 그냥 text[i]로 줄여 씁니다. '\0'은 0이고 따라서 거짓이기 때문입니다. 둘 다 맞지만, 배우는 동안에는 명시적인 형태가 읽기 쉽습니다.

인덱스 대신 포인터로 훑을 수도 있고, 그것도 똑같이 관용적인 C입니다.

toupper를 호출하기 전의 unsigned char 캐스팅은 장식이 아닙니다. <ctype.h> 함수들은 음수 값에 대해 미정의이고, 평범한 char는 127을 넘는 바이트에 대해 음수가 될 수 있습니다.

=로 문자열을 복사할 수 없습니다

이것이 모든 입문자가 부딪히는 벽입니다.

char a[10];
a = "hello";        /* 오류: 배열 타입 식에 대입 */

char b[10] = "hi";
char c[10];
c = b;              /* 오류 - 같은 이유 */

배열 이름은 수정 가능한 값이 아니므로 =가 할 일이 없습니다. 이 축약이 통하는 유일한 자리는 선언 시 초기화이고, 그것은 컴파일러가 수행하는 다른 연산입니다.

실행 중에 복사하려면 <string.h>strcpy로 바이트를 복사하세요.

같은 이유로 ==가 문자열을 비교하지 않습니다. if (a == b)는 두 주소를 비교하므로, 같은 텍스트를 담은 서로 다른 배열 둘에 대해서는 거짓입니다. 대신 strcmp(a, b) == 0을 쓰세요. 두 함수와 더 안전한 친척들은 문자열 함수에서 다룹니다.

문자열의 배열

문자열 목록은 포인터의 배열(읽기만 하는 고정 텍스트용)이거나 2차원 char 배열(각 항목을 수정해야 할 때)입니다.

포인터 배열은 읽기 전용 리터럴을 가리키는 주소 셋을 저장합니다. 간결하지만 수정할 수 없습니다. char[3][16] 형태는 내 메모리 48바이트를 잡으므로 각 행을 다시 쓸 수 있습니다.

흔한 실수

  • 종료 문자의 바이트를 잊음. char s[5] = "hello"에는 '\0'이 들어갈 자리가 없습니다. strlen + 1 크기로 잡으세요.
  • strlen을 뜻하면서 sizeof를 씀. 포인터에 sizeof를 쓰면 텍스트 길이가 아니라 포인터의 크기가 나옵니다.
  • 리터럴을 가리키는 char *를 통해 씀. 그런 포인터는 const char *로 선언하세요.
  • ==로 비교. 그것은 주소를 비교합니다. strcmp를 쓰세요.
  • scanf("%s", buf)로 읽기. 크기 제한이 없어 기꺼이 넘칩니다. fgets를 선호하세요.

자주 묻는 질문

C에서 문자열은 어떻게 선언하나요?

char 배열로 선언합니다. char name[] = "Ada";처럼요. C에는 내장 문자열 타입이 없습니다. 이 배열은 'A', 'd', 'a'와 끝을 표시하는 네 번째 숨은 바이트 '\0'을 담습니다. 나중에 내용을 바꿀 여유가 필요하면 크기를 명시하세요. char name[32] = "Ada";처럼요.

C의 널 종료 문자란 무엇인가요?

문자열이 끝나는 자리를 표시하는 바이트 '\0'(값 0)입니다. printf("%s"), strlen, strcpy 같은 모든 표준 함수가 그 바이트를 만날 때까지 앞으로 읽어 나갑니다. 이것이 없는 char 배열은 문자열이 아니며, 그런 함수에 넘기면 메모리 끝을 넘어 읽습니다.

C에서 왜 =로 문자열을 대입할 수 없나요?

배열은 =로 복사할 수 있는 값이 아니기 때문입니다. char a[10]; a = "hi";는 컴파일되지 않습니다. 대신 <string.h>strcpy(a, "hi")로 문자들을 복사하거나, 선언할 때 초기화하세요. char a[10] = "hi";는 이 축약이 통하는 유일한 자리입니다.

C에서 char *와 char []의 차이는 무엇인가요?

char s[] = "hi"는 텍스트의 개인 사본을 담은 수정 가능한 배열을 만듭니다. char *p = "hi"는 읽기 전용 메모리에 있는 문자열 리터럴을 가리키는 포인터를 만듭니다. 읽는 것은 괜찮지만 그것을 통해 쓰면 죽습니다. 수정할 생각이면 배열을, 읽기만 할 거면 포인터를 쓰세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기