[C] 문자열 상수와 문자열 변수의 차이

문자열 상수와 문자열 변수

문자열을 초기화하는 다음 두 가지 방법은 거의 비슷해 보이지만 근본적으로 완전히 다른 동작을 한다.

char *s1 = "abcdefg";
char s2[8] = "abcdefg";

첫번째 s1은 프로그램이 로딩될 때 정적영역에 "abcdefg"를 저장한 다음 이 시작 주소를 s1에 대입한다. 문자열 포인터는 s1에는 나중에 다른 주소의 값을 대입할 수 있다. 즉, s1자체는 변경이 가능한 포인터 변수이지만, 지금 s1이 가리키고 있는 문자열은 문자열 상수이므로 이 문자열을 변경할 수 없다. 반대로 s2는 힙 영역에 메모리를 할당한 후 "abcdefg"를 이곳에 저장했다. 따라서 s2에 담겨 있는 문자열은 변경이 가능하다. (각각의 바이트는 모두 개별 변수처럼 취급되므로)

간단한 예시를 보자. 다음 함수는 문자열을 모두 대문자로 만들어 주는 함수이다. (윈도우용 C컴파일러에 있는 함수임)

#include <ctype.h>
void strupr(char *origin) {
    char *temp = origin;
    while(*temp != '\0') {
        *temp = toupper(*temp);
    }
}

이 함수는 문자열 포인터(=문자열의 시작번지)를 인수로 받아, 각 문자를 대문자로 바꿔준다. toupper 함수는 표준 C 라이브러리에 포함되어 있는 함수이다. 만약 이 함수에 s1을 넘기면 프로그램이 다운된다. 문자열 상수를 변경하려는 시도를 했기 때문이다. 반대로 s2를 넘긴 경우에는 정상적으로 동작하는 것을 볼 수 있다. 다음은 전체 코드이다.

#include <stdio.h>
#include <ctype.h>

void strupr(char *origin);

int main(void) {
    char *s1 = "abcdefg";
    char s2[8] = "abcdefg";
    //printf("%s\n", strupr(s1)); // ==> 프로그램이 다운된다. 
    printf("%s\n", strupr(s2)); // ==> 정상적으로 동작한다. 
    return 0;
}

void strupr(char *origin) {
    char *temp = origin;
    while(*temp != '\0') {
        *temp = toupper(*temp);
    }
}

만약 문자열 상수로 만들어진 문자열을 사용하고자 한다면 다음과 같이 고친다.
char *s1 = "abcdefg";
char s2[8];
strcpy(s2,s1);

언뜻 생각하기에는 s2 = s1; 이라고 하면 안되나? 하는 생각이 들 수 있는데, 안된다. 비록 문자열 배열 변수의 변수명이 배열의 시작번지를 가리킨다고 해서 이것이 포인터라는 것은 아니다. 그것은 일종의 convension으로 배열의 변수명을 배열의 시작번지와 같이 다루는 것일 뿐이다. 즉, s2는 포인터 상수이고, 방금 예를 든 저 구문은 s2의 자리에 s1의 값 (정적영역내의 "abcdefg"가 자리잡은 메모리 번지)을 대입하고자 하는 시도이고, 이는 서로 타입이 맞지 않는 대입 구문이기 때문에 에러를 유발한다.

정리

  • 문자열 포인터로 문자열을 초기화하면 문자열 상수가 된다.
  • 배열로 선언한 문자열에 문자열을 집어넣을 때는 직접 대입이 아니라 strcpy를 쓰거나 한자 한자 루프를 돌면서 넣어야 한다.