C 문자열은 별도 타입이 아니라 널 종료된 char 배열이라는 전제부터 잡아야 합니다.
C 문자열 카드는 함수 목록보다 문자열이 실제로 어떻게 표현되는가를 이해하는 카드입니다.
- 별도 문자열 타입은 없다
- 끝에
'\0'가 있어야 문자열로 읽힌다 - 비교는 주소가 아니라 내용으로 해야 한다
- 복사와 입력은 버퍼 크기와 같이 봐야 한다
숏컷 코드
char name[20] = "Mina";
size_t len = strlen(name);
int same = (strcmp(name, "Mina") == 0);문법
C 문자열은 결국 문자 배열 + 널 종료 문자 '\0' 입니다. 그래서 길이, 비교, 복사, 입력이 모두 이 규약을 전제로 움직입니다.
char s[] = "Hello";
char t[6] = {'H', 'e', 'l', 'l', 'o', '\0'};두 선언은 같은 문자열을 표현합니다. sizeof(s)는 배열 전체 바이트 수를 보고, strlen(s)는 널 종료 전까지의 문자 개수를 셉니다.
printf("%zu\n", sizeof(s)); // 6
printf("%zu\n", strlen(s)); // 5기본 작업
배열/포인터를 ==로 비교하면 내용이 아니라 주소를 비교하게 됩니다.
문자열이 같은지 보려면 strcmp 계열을 써야 합니다.
char a[] = "cat";
char b[] = "cat";
if (strcmp(a, b) == 0) {
/* 문자열 내용이 같음 */
}반대로 이 비교는 의도와 다릅니다.
if (a == b) {
/* 같은 주소인지 비교 */
}복사도 같은 원리입니다. 문자열 함수는 대부분 목적지 버퍼 크기를 자동으로 모르기 때문에, 복사 그 자체보다 버퍼 경계를 같이 추적하는 습관이 더 중요합니다.
char src[] = "hello";
char dst[16];
snprintf(dst, sizeof(dst), "%s", src);입력과 버퍼
char buf[] = "hello";는 수정 가능한 배열 복사본이고, const char *p = "hello";는 읽기 전용 리터럴을 가리키는 경우가 많습니다.
이 차이를 놓치면 수정 시점에 바로 위험해집니다.
char literal[] = "hello";
literal[0] = 'H'; // 가능
const char *view = "hello";
/* view[0] = 'H'; */ // 위험입력은 더 민감합니다. %s는 공백에서 끊기고 길이 제한을 빼먹으면 바로 버퍼를 넘깁니다. 줄 입력이 필요하거나 안전성이 중요하면 fgets가 기본에 가깝습니다.
char line[32];
fgets(line, sizeof(line), stdin);이 패턴은 공백을 포함한 줄 입력을 받을 수 있다는 점에서도 %s보다 낫습니다.
체크포인트
- 문자열 길이를 센다:
strlen - 문자열 내용을 비교한다:
strcmp(...) == 0 - 버퍼에 복사한다: 길이 제한과 널 종료 확인
- 입력을 받는다: 버퍼 크기와 함께 읽는다
- 문자열 리터럴을 받는다:
const char * sizeof와strlen은 묻는 질문이 다르다
주의할 점
버퍼 오버플로우는 C 문자열 처리에서 가장 흔하고 위험한 버그입니다.
char name[10];
// ❌ 길이 제한 없는 입력
scanf("%s", name);
// ✅ 길이 제한 포함
scanf("%9s", name);
// ✅ 또는 fgets 사용
fgets(name, sizeof(name), stdin);또한 문자열 비교는 ==가 아니라 strcmp로 해야 하고, 널 종료가 깨진 버퍼에 strlen을 쓰면 끝을 넘어 읽게 됩니다.
참고 링크
2 sources