4단원 — 문자열
C에는 별도의 "문자열" 자료형이 없습니다. 문자열은 **`'\0'`(널 문자)로 끝나는 `char` 배열**입니다. `<string.h>`의 표준 함수를 활용해 길이/복사/비교/연결을 다룹니다.
이 강의에서 배우는 것
- 1C 문자열의 **널 종료(null-terminated)** 규칙을 이해한다.
- 2`<string.h>`의 `strlen`, `strcpy`, `strcat`, `strcmp`를 사용한다.
- 3`fgets`로 안전하게 한 줄을 입력받는다.
- 4버퍼 오버플로의 위험을 인지한다.
왜 "문자열"이 별도 타입이 아닐까?
C는 작고 빠른 언어로 설계되어, 문자열도 **단순한 char 배열**로 표현합니다. 배열의 끝을 알릴 방법이 필요해서 도입된 것이 **널 문자(`'\0'`)** 입니다.
char s[] = "Hi";은 메모리에서 다음과 같이 저장됩니다.
s[0] s[1] s[2]
┌────┬────┬─────┐
│ H │ i │ \0 │ ← '\0'이 "여기서 끝!"이라는 표시
└────┴────┴─────┘`strlen("Hi")` 가 2를 돌려주는 이유: `'\0'`을 만날 때까지 글자 수를 셈.
핵심 개념
1) 문자열 표현
char s1[] = "Hello"; // 6 byte ('H','e','l','l','o','\0')
char s2[10] = "Hi"; // 나머지는 '\0'으로 채워짐
char s3[] = {'H','i','\0'}; // 동치 표현2) `<string.h>`의 핵심 함수
| 함수 | 의미 | 주의 |
|---|---|---|
| `strlen(s)` | 길이 (널 제외) | s가 NULL이면 안 됨 |
| `strcpy(dst, src)` | src를 dst에 복사 | dst 버퍼가 충분해야 함 |
| `strcat(dst, src)` | dst 끝에 src 이어 붙임 | 합쳐진 길이 + 1 만큼 공간 필요 |
| `strcmp(a, b)` | 사전순 비교 | 같으면 0, a<b면 음수, a>b면 양수 |
3) 한 줄 입력은 `fgets`로
char buf[128];
fgets(buf, sizeof(buf), stdin); // 줄바꿈 포함
buf[strcspn(buf, "\n")] = '\0'; // 줄바꿈 제거`scanf("%s", ...)` 는 공백을 만나면 끊어지고, 입력 길이도 검사하지 않아 **버퍼 오버플로** 위험이 있습니다.
4) 문자 단위 처리(`<ctype.h>`)
| 함수 | 설명 |
|---|---|
| `isalpha(c)` | 알파벳? |
| `isdigit(c)` | 숫자? |
| `tolower(c)` / `toupper(c)` | 소/대문자 변환 |
예제로 보기
예제 1 — `ex01_basic.c` : 길이와 한 글자씩 보기
**실행 결과**
문자열: Hello, C!
길이: 9
s[0] = H (코드 72)
s[1] = e (코드 101)
s[2] = l (코드 108)
s[3] = l (코드 108)
s[4] = o (코드 111)
s[5] = , (코드 44)
s[6] = (코드 32)
s[7] = C (코드 67)
s[8] = ! (코드 33)핵심: 문자열은 결국 `char` 정수의 배열이며, 끝의 `'\0'`은 길이에 포함되지 않습니다.
예제 2 — `ex02_strops.c` : 복사·연결·비교
char a[64] = "Hello";
char b[] = ", World!";
char c[64];
strcpy(c, a);
strcat(a, b);**실행 결과**
a = Hello, World!
c = Hello
strcmp("abc", "abd") = -1
strcmp("abc", "abc") = 0핵심: `strcat`은 dst의 **널 위치부터** 이어 붙이므로 dst가 유효한 문자열이어야 합니다.
예제 3 — `ex03_fgets.c` : 안전한 입력
**입력**: `hello c world`
문장 입력: 입력값: "hello c world"
길이: 13핵심: `fgets`는 버퍼 크기를 받기 때문에 오버플로가 나지 않습니다.
예제 4 — `ex04_count.c` : 모음/자음 카운트
**입력**: `Hello World`
영문 문장 입력: 모음: 3, 자음: 7핵심: `tolower`로 대소문자를 통일한 뒤 비교하면 분기가 절반으로 줄어듭니다.
다른 시각으로 보기 — 문자열 연산이 느려 보이는 이유
`strlen`을 호출할 때마다 컴파일러는 처음부터 `'\0'`을 찾습니다.
for (int i = 0; i < strlen(s); i++) // 매 반복마다 길이 재계산!성능이 중요할 때는 길이를 한 번만 구합니다.
size_t n = strlen(s);
for (size_t i = 0; i < n; i++) ...C 문자열은 **끝을 모르는 배열**이라는 점이 효율과 안전 양쪽에 영향을 줍니다.
자주 하는 실수
- **버퍼 부족**: `char dst[3]; strcpy(dst, "Hello");` → 인접 메모리 손상.
- **널 종료 누락**: `char s[5]; s[0]='H'; s[1]='i';` 만 하면 끝이 어디인지 모름 → 쓰레기 출력.
- **`==`로 문자열 비교**: 두 포인터를 비교하므로 같은 내용이어도 false. 반드시 `strcmp`.
- **`fgets` 후 줄바꿈 잔존**: 끝에 `\n`이 남아 출력이 한 줄 더 내려감 → `strcspn`으로 제거.
- **상수 문자열 수정**: `char *p = "hi"; p[0] = 'H';` 는 정의되지 않은 동작. 수정하려면 `char p[] = "hi";`.
정리
- C 문자열은 `'\0'`로 끝나는 `char` 배열이다.
- 길이/복사/비교/연결은 `<string.h>` 함수로.
- `scanf("%s")`보다 `fgets`가 안전.
- `strcat`/`strcpy`는 dst 버퍼가 충분한지 책임은 호출자에게.
- 문자열 비교는 `strcmp`, 절대 `==`.
직접 해 보기
cd src
gcc -std=c11 -Wall -o ex01 ex01_basic.c && ./ex01
gcc -std=c11 -Wall -o ex02 ex02_strops.c && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_fgets.c && echo "hello c world" | ./ex03
gcc -std=c11 -Wall -o ex04 ex04_count.c && echo "Hello World" | ./ex04응용:
- `ex01`에서 `s[2] = '\0';` 한 줄을 추가한 뒤 출력해 보세요. 문자열이 어떻게 끊기는지 관찰.
- `ex02`에서 `c[]` 버퍼를 너무 작게(예: `char c[3]`) 잡고 복사하면 어떤 경고/오류가 뜰까요?
💻 예제 (examples)
실제로 컴파일·실행해 결과를 확인할 수 있는 예제입니다.
#include <stdio.h>
#include <string.h>
int main(void) {
char s[] = "Hello, C!";
printf("문자열: %s\n", s);
printf("길이: %zu\n", strlen(s));
/* 한 글자씩 출력 */
for (size_t i = 0; s[i] != '\0'; i++) {
printf("s[%zu] = %c (코드 %d)\n", i, s[i], s[i]);
}
return 0;
}
문자열: Hello, C!
길이: 9
s[0] = H (코드 72)
s[1] = e (코드 101)
s[2] = l (코드 108)
s[3] = l (코드 108)
s[4] = o (코드 111)
s[5] = , (코드 44)
s[6] = (코드 32)
s[7] = C (코드 67)
s[8] = ! (코드 33)#include <stdio.h>
#include <string.h>
int main(void) {
char a[64] = "Hello";
char b[] = ", World!";
char c[64];
strcpy(c, a);
strcat(a, b);
printf("a = %s\n", a);
printf("c = %s\n", c);
printf("strcmp(\"abc\", \"abd\") = %d\n", strcmp("abc", "abd"));
printf("strcmp(\"abc\", \"abc\") = %d\n", strcmp("abc", "abc"));
return 0;
}
a = Hello, World!
c = Hello
strcmp("abc", "abd") = -1
strcmp("abc", "abc") = 0#include <stdio.h>
#include <string.h>
int main(void) {
char line[128];
printf("문장 입력: ");
if (fgets(line, sizeof(line), stdin) == NULL) return 1;
line[strcspn(line, "\n")] = '\0'; /* 끝의 \n 제거 */
printf("입력값: \"%s\"\n", line);
printf("길이: %zu\n", strlen(line));
return 0;
}
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int main(void) {
char buf[256];
printf("영문 문장 입력: ");
if (fgets(buf, sizeof(buf), stdin) == NULL) return 1;
buf[strcspn(buf, "\n")] = '\0';
int vowels = 0, consonants = 0;
for (size_t i = 0; buf[i] != '\0'; i++) {
char ch = (char)tolower((unsigned char)buf[i]);
if (ch >= 'a' && ch <= 'z') {
if (ch == 'a' || ch == 'e' || ch == 'i' ||
ch == 'o' || ch == 'u')
vowels++;
else
consonants++;
}
}
printf("모음: %d, 자음: %d\n", vowels, consonants);
return 0;
}
📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
문제 1 (hw01.c)
목표: 영문 문자열을 입력받아 **모두 대문자로** 변환해 출력하세요. (`<ctype.h>`의 `toupper` 사용)
- 파일명: hw01.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int main(void) {
char buf[256];
printf("문장 입력: ");
if (fgets(buf, sizeof(buf), stdin) == NULL) return 1;
buf[strcspn(buf, "\n")] = '\0';
for (size_t i = 0; buf[i] != '\0'; i++)
buf[i] = (char)toupper((unsigned char)buf[i]);
printf("%s\n", buf);
return 0;
}
문제 2 (hw02.c)
목표: 문자열을 입력받아 **회문(palindrome)** 인지 판정하세요. 대소문자 구분 없이.
- 파일명: hw02.c
입력: Level
회문 입니다.▶정답 코드 펼치기 / 접기
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int main(void) {
char buf[256];
printf("문장 입력: ");
if (fgets(buf, sizeof(buf), stdin) == NULL) return 1;
buf[strcspn(buf, "\n")] = '\0';
int len = (int)strlen(buf);
int palindrome = 1;
for (int i = 0, j = len - 1; i < j; i++, j--) {
char a = (char)tolower((unsigned char)buf[i]);
char b = (char)tolower((unsigned char)buf[j]);
if (a != b) { palindrome = 0; break; }
}
printf("%s\n", palindrome ? "회문 입니다." : "회문이 아닙니다.");
return 0;
}
입력: Level
회문 입니다.문제 3 (hw03.c)
목표: 문자열에서 특정 문자 하나(`char`)의 등장 횟수를 출력하세요.
- 파일명: hw03.c
문장: hello world
찾을 문자: l
'l'는 3번 나타납니다.▶정답 코드 펼치기 / 접기
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[256];
char target;
printf("문장: ");
if (fgets(buf, sizeof(buf), stdin) == NULL) return 1;
buf[strcspn(buf, "\n")] = '\0';
printf("찾을 문자: ");
scanf(" %c", &target);
int count = 0;
for (size_t i = 0; buf[i] != '\0'; i++)
if (buf[i] == target) count++;
printf("'%c'는 %d번 나타납니다.\n", target, count);
return 0;
}
문장: hello world
찾을 문자: l
'l'는 3번 나타납니다.