개발이야기/C언어

[C언어] 문자열과 포인터(char *str) 쉽게 이해하기

Study & Stack 2025. 7. 1. 20:45
728x90

문자열을 다룰 때 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 등)를 구현하거나 쓸 때도, 이 구조를 이해하고 있으면 어떤 함수가 메모리를 읽고 쓰는지 정확히 파악할 수 있습니다.

728x90