[C언어] 문자열과 포인터(char *str) 쉽게 이해하기
문자열을 다룰 때 C 언어는 다른 언어와 조금 다릅니다. Python, Java처럼 문자열이 객체로 자동 관리되지 않기 때문에, C에서는 문자열도 결국 '문자 배열'과 '포인터'로 다뤄야 하죠.
이번 글에서는 char *str 형태의 포인터가 문자열을 어떻게 다루는지, 그리고 배열과 어떤 관계가 있는지 알아봅니다.
🧠 문자열 = 문자 배열
C에서 문자열은 문자들의 배열입니다. 예를 들어:
char str1[] = "Hello";
이 선언은 메모리에 다음과 같이 저장됩니다:
인덱스 | 0 | 1 | 2 | 3 | 4 | 5 |
값 | H | e | l | l | o | \0 |
마지막 \0은 **널 종료 문자(null terminator)**로 문자열 끝을 알립니다. C에서 문자열은 단순히 문자 배열이지만, \0이 포함되어야만 문자열로 인식됩니다. 이 \0 덕분에 printf("%s", str1); 같은 함수가 문자열의 끝을 인식할 수 있는 것이죠.
✅ 정리: 문자열은 문자 배열 + 널 종료 문자가 결합된 구조입니다.
🔗 문자열 포인터 char *str
C에서는 문자열을 다음처럼 포인터로 선언할 수도 있어요:
char *str2 = "World";
여기서 str2는 "World"라는 문자열이 저장된 어딘가의 메모리 주소를 가리킵니다. 즉,
- str2는 포인터이고,
- *str2 == 'W', *(str2 + 1) == 'o' 와 같이 접근할 수 있습니다.
이 방식은 매우 메모리 효율적이고, 상수 문자열 리터럴을 재사용하는 데 유리합니다.
📌 주의할 점은 문자열 리터럴은 일반적으로 **읽기 전용 메모리 영역(read-only section)**에 저장되므로, char *str = "Hello";로 선언된 문자열을 수정하려고 하면 **정의되지 않은 동작(undefined behavior)**이 발생할 수 있다는 점입니다.
❗ 문자열 상수는 수정하면 안 되는 이유
char *str = "Hello";
str[0] = 'J'; // 위험! 프로그램이 크래시날 수도 있음
이 코드는 컴파일러에 따라 경고 없이 컴파일될 수도 있지만, 실행하면 **세그멘테이션 폴트(segmentation fault)**가 발생할 수 있습니다. 이유는 문자열이 읽기 전용 영역에 존재하기 때문입니다.
📋 예제: 배열 vs 포인터 문자열 선언
#include <stdio.h>
int main() {
char str1[] = "Hello"; // 수정 가능
char *str2 = "World"; // 수정하면 위험
printf("str1: %s\n", str1);
printf("str2: %s\n", str2);
str1[0] = 'Y'; // OK
// str2[0] = 'M'; // 위험: 문자열 상수 변경은 undefined behavior
printf("Modified str1: %s\n", str1);
// printf("Modified str2: %s\n", str2); // 비추천
return 0;
}
🔍 출력 예시
str1: Hello
str2: World
Modified str1: Yello
이 예제에서 str1은 스택에 저장된 배열이기 때문에 자유롭게 수정할 수 있고, str2는 문자열 상수를 가리키는 포인터이기 때문에 직접 수정하는 것은 매우 위험합니다.
🔄 포인터를 이용한 문자열 탐색
포인터를 이용하면 문자열을 배열처럼 다룰 수 있을 뿐만 아니라, 루프를 통해 문자열의 각 문자에 순차적으로 접근할 수 있습니다.
char *str = "Pointer";
while (*str != '\0') {
printf("%c ", *str);
str++;
}
이 코드는 str이 가리키는 주소의 값을 출력하고, 한 글자씩 다음 주소로 이동하며 \0을 만날 때까지 반복합니다. 이 방식은 문자열 처리의 핵심 개념 중 하나입니다.
✅ 문자열 포인터의 특징 정리
형태 | 설명 |
char str[] | 문자열 복사본을 스택(stack)에 배열로 저장 |
char *str | 문자열 상수의 주소를 가리킴 (읽기 전용) |
*(str + i) | 문자열의 i번째 문자에 접근 |
문자열은 \0으로 종료 | 문자열 끝을 나타내는 null 문자 포함 필요 |
❓ char[]와 char* 중 무엇을 쓸까?
상황 | 추천 형태 | 이유 |
문자열을 수정할 필요가 있는 경우 | char str[] = "hello"; | 스택에 복사된 배열이므로 수정 가능 |
문자열을 읽기만 할 경우 | char *str = "hello"; | 메모리 절약, 상수 공유 가능 |
📌 배열은 수정 가능하고 독립적인 복사본, 포인터는 가볍고 효율적이지만 수정 위험 존재
📦 마무리
C에서 문자열은 "문자 배열"이자 "포인터의 대표적인 예시"입니다.
처음엔 char *str가 낯설지만, 결국엔 배열처럼 동작하고
또 배열 이름도 포인터처럼 쓰인다는 것을 이해하면
문자열과 포인터의 연결도 어렵지 않게 느껴질 거예요.
문자열을 복사하고 다루기 위한 함수(strcpy, strlen, strcmp 등)를 구현하거나 쓸 때도, 이 구조를 이해하고 있으면 어떤 함수가 메모리를 읽고 쓰는지 정확히 파악할 수 있습니다.