Skip to content

6. C/C++의 문자열 리터럴과 문자 배열


1. 문자열 리터럴

  • 문자열 리터럴의 경우 const char *로 표현되며, 읽기 전용 메모리에 저장되어 포인터를 통해 읽어올 수 있다.


#include <stdio.h>
#include <string.h>

int main()
{
    const char *s1 = "Hello";
    char *s2 = "Hello";

    printf("char 타입의 크기: %lu\n\n", sizeof(char));

    printf("s1의 크기: %lu\n", sizeof(s1));
    printf("s1의 길이: %lu\n", strlen(s1));
    printf("s1이 가리키는 메모리 주소: %p\n\n", s1);

    printf("s2의 크기: %lu\n", sizeof(s2));
    printf("s2의 길이: %lu\n", strlen(s2));
    printf("s2이 가리키는 메모리 주소: %p\n\n", s2);

    return 0;
}

// char 타입의 크기: 1
//
// s1의 크기: 8
// s1의 길이: 5
// s1이 가리키는 메모리 주소: 0x100003ee8
//
// s2의 크기: 8
// s2의 길이: 5
// s2이 가리키는 메모리 주소: 0x100003ee8


  • 이때 s1s2const 키워드의 유무 차이만 있을 뿐 여전히 수정이 불가능한 값이다.
  • s1s2는 포인터이기 때문에 sizeof의 결과가 문자열 리터럴의 길이가 아닌 8바이트로 나오는 것을 확인할 수 있다.
  • 대부분의 컴파일러는 문자열 리터럴을 Release 빌드에서 같은 주소 값을 가지도록 최적화된다.


2. 문자 배열

  • C/C++에서는 문자열 리터럴과 문자로 이루어진 배열(문자 배열)은 다르게 동작한다.


#include <stdio.h>
#include <string.h>

int main()
{
    char *s1 = "Hello";
    char s2[] = "Hello";
    char s3[] = {'H', 'e', 'l', 'l', 'o', '\0'};
    char s4[] = {'H', 'e', 'l', 'l', 'o'};

    printf("s1의 크기: %lu\n", sizeof(s1));
    printf("s1의 길이: %lu\n", strlen(s1));
    printf("s1이 가리키는 메모리 주소: %p\n\n", s1);

    printf("s2의 크기: %lu\n", sizeof(s2));
    printf("s2의 길이: %lu\n", strlen(s2));
    printf("s2이 가리키는 메모리 주소: %p\n\n", s2);

    printf("s3의 크기: %lu\n", sizeof(s3));
    printf("s3의 길이: %lu\n", strlen(s3));
    printf("s3이 가리키는 메모리 주소: %p\n\n", s3);

    printf("s4의 크기: %lu\n", sizeof(s4));
    printf("s4의 길이: %lu\n", strlen(s4));
    printf("s4이 가리키는 메모리 주소: %p\n\n", s4);

    return 0;
}

// s1의 크기: 8
// s1의 길이: 5
// s1이 가리키는 메모리 주소: 0x100003e68
//
// s2의 크기: 6
// s2의 길이: 5
// s2이 가리키는 메모리 주소: 0x3041272ba
//
// s3의 크기: 6
// s3의 길이: 5
// s3이 가리키는 메모리 주소: 0x3041272b4
//
// s4의 크기: 5
// s4의 길이: 10
// s4이 가리키는 메모리 주소: 0x3041272af


  • s1은 읽기 전용 메모리에 저장된 문자열 리터럴의 시작 위치를 가리키는 포인터이다.
  • 포인터이기 때문에 sizeof의 결과가 문자열 리터럴의 길이가 아닌 8바이트로 나오는 것을 확인할 수 있다.
  • s2, s3, s4는 스택에 저장된 배열이다.
  • 포인터와 다르게 배열은 sizeof의 결과가 배열의 크기(배열 요소의 개수)로 나온다.
  • 여기서 s2"Hello"는 문자열 리터럴처럼 보이지만 사실 컴파일러에 의해 {'H', 'e', 'l', 'l', 'o', '\0'} 형태로 치환된다.
  • 즉, s2s3는 같은 구문이다.
  • 그리고 문자 배열을 사용할 때 주의할 점은 '\0'이 마지막 요소가 되어야 한다는 것이다.
  • s4를 보면 '\0'이 마지막 요소가 아니기 때문에 strlen이 올바른 문자열 길이를 출력하지 못한다는 것을 확인할 수 있다.


3. 문자열 리터럴과 동적 할당된 문자 배열

  • 동적으로 메모리를 할당한 후 포인터로 동적 할당된 메모리 주소를 가리키도록 해보자.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char *s1 = "Hello";
    char *s2 = malloc(sizeof(char) * 6);

    strcpy(s2, "Hello");

    printf("s1의 크기: %lu\n", sizeof(s1));
    printf("s1의 길이: %lu\n", strlen(s1));
    printf("s1이 가리키는 메모리 주소: %p\n\n", s1);

    printf("s2의 크기: %lu\n", sizeof(s2));
    printf("s2의 길이: %lu\n", strlen(s2));
    printf("s2이 가리키는 메모리 주소: %p\n\n", s2);

    free(s2);

    return 0;
}

// s1의 크기: 8
// s1의 길이: 5
// s1이 가리키는 메모리 주소: 0x100003f0e
//
// s2의 크기: 8
// s2의 길이: 5
// s2이 가리키는 메모리 주소: 0x1090040e0


  • 문자열 리터럴과 동적 할당된 문자 배열은 모두 첫 번째 문자의 주소를 가리키는 포인터로 표현된다.
  • s1s2는 모두 포인터이기 때문에 sizeof의 결과가 문자열 리터럴의 길이가 아닌 8바이트로 나오는 것을 확인할 수 있다.


#include <stdio.h>

int main()
{
    char *s1 = "Hello";

    s1[4] = '!';

    printf("s1: %s", s1);

    return 0;
}


  • s1은 문자열 리터럴 "Hello"의 메모리 주소를 가리키고 있다.
  • 이때 s1의 마지막 문자를 '!'로 변경하려고 하면 에러가 발생한다.
  • 문자열 리터럴은 읽기 전용 메모리 저장되어 있기 때문에 접근하여 수정할 수 없다.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char *s2 = malloc(sizeof(char) * 6);
    strcpy(s2, "Hello");

    s2[4] = '!';

    printf("s2: %s\n", s2);

    return 0;
}

// s2: Hell!


  • s2은 동적 할당된 메모리 주소를 가리키고 있다.
  • 이때 strcpy"Hello"를 저장한 후 마지막 문자를 '!'로 변경하니 문제가 없다.
  • 동적 할당된 문자 배열은 힙에 저장되어 있기 때문에 접근하여 수정할 수 있다.


#include <stdio.h>
#include <string.h>

int main()
{
    char *s1 = "Hello";

    printf("s1의 크기: %lu\n", sizeof(s1));
    printf("s1의 길이: %lu\n", strlen(s1));
    printf("s1이 가리키는 메모리 주소: %p\n\n", s1);

    s1 = "Bye";

    printf("s1의 크기: %lu\n", sizeof(s1));
    printf("s1의 길이: %lu\n", strlen(s1));
    printf("s1이 가리키는 메모리 주소: %p\n\n", s1);

    return 0;
}

// s1의 크기: 8
// s1의 길이: 5
// s1이 가리키는 메모리 주소: 0x100003f5c
//
// s1의 크기: 8
// s1의 길이: 3
// s1이 가리키는 메모리 주소: 0x100003fb2


  • 물론 위의 코드처럼 s1이 가리키는 문자열 리터럴의 메모리 주소를 다른 문자열 리터럴의 메모리 주소로 변경하는 것은 가능하다.

References