문자 상수(Character constant)

문자 상수(Character constant)

'a'처럼 작은따옴표로 문자 하나를 감싼 것을 **문자 상수(character constant)**라고 해요. 이 값은 단순히 그 "문자를 담은 것"이 아니라, 문자들이 실행 환경에서 어떻게 인코딩됐는지에 따라 결정되는 정수예요. C23까지 문자 상수의 종류와 타입이 어떻게 달라지는지, 그리고 여러 문자를 한 상수에 담는 멀티 문자 상수까지 함께 살펴볼게요.

출처: cppreference

본문

문법

' c-char ' (1)
u8' c-char ' (2) (C23부터)
u' c-char ' (3) (C11부터)
U' c-char ' (4) (C11부터)
L' c-char ' (5)
' c-char-sequence ' (6)
L' c-char-sequence ' (7)
u' c-char-sequence ' (8) (C11부터)(C23에서 제거)
U' c-char-sequence ' (9) (C11부터)(C23에서 제거)

여기서

  • c-char는 다음 중 하나예요.
    • 기본 소스 문자 집합에서 작은따옴표('), 백슬래시(\), 개행 문자를 뺀 문자.
    • 이스케이프 시퀀스: 특수 문자 이스케이프 \' \" \? \\ \a \b \f \n \r \t \v, 16진수 이스케이프 \x..., 또는 8진수 이스케이프 \... (이스케이프 시퀀스에서 정의됨).
    • 유니버설 문자 이름(universal character name): \u... 또는 \U... (이스케이프 시퀀스에서 정의됨). (C99부터)
  • c-char-sequence는 둘 이상의 c-char로 이뤄진 시퀀스예요.
  1. 단일 바이트 정수 문자 상수. 예: 'a', '\n', '\13'. 타입은 int이고, 값은 c-char가 char 타입 값으로서 실행 문자 집합에서 가지는 표현을 int로 매핑한 것과 같아요. c-char가 실행 문자 집합에서 단일 바이트로 표현될 수 없다면 값은 구현 정의예요.

  2. UTF-8 문자 상수. 예: u8'a'. 타입은 char8_t이고, 값은 c-char의 ISO 10646 코드 포인트 값과 같아요(코드 포인트 값이 단일 UTF-8 코드 단위로 표현 가능한 경우, 즉 c-char가 0x0~0x7F 범위일 때). c-char가 단일 UTF-8 코드 단위로 표현될 수 없다면 그 프로그램은 ill-formed예요.

  3. 16비트 와이드 문자 상수. 예: u'貓'(단, u'🍌'는 아님 — u'\U0001f34c'). 타입은 char16_t이고, 값은 c-char가 mbrtoc16이 만들어 내는 16비트 인코딩(보통 UTF-16)에서 가지는 값과 같아요. c-char를 표현할 수 없거나 두 개 이상의 16비트 문자로 매핑된다면 값은 구현 정의예요.

  4. 32비트 와이드 문자 상수. 예: U'貓' 또는 U'🍌'. 타입은 char32_t이고, 값은 c-char가 mbrtoc32가 만들어 내는 32비트 인코딩(보통 UTF-32)에서 가지는 값과 같아요. c-char를 표현할 수 없거나 두 개 이상의 32비트 문자로 매핑된다면 값은 구현 정의예요. (C23 이전)

  5. (C23부터) UTF-16 문자 상수. 예: u'貓'(단, u'🍌'는 아님 — u'\U0001f34c'). 타입은 char16_t이고, 값은 c-char의 ISO 10646 코드 포인트 값과 같아요(코드 포인트 값이 단일 UTF-16 코드 단위로 표현 가능한 경우, 즉 c-char가 0x00xD7FF 또는 0xE0000xFFFF 범위일 때). c-char가 단일 UTF-16 코드 단위로 표현될 수 없다면 그 프로그램은 ill-formed예요. (C23부터)

  6. (C23부터) UTF-32 문자 상수. 예: U'貓' 또는 U'🍌'. 타입은 char32_t이고, 값은 c-char의 ISO 10646 코드 포인트 값과 같아요(코드 포인트 값이 단일 UTF-32 코드 단위로 표현 가능한 경우, 즉 c-char가 0x00xD7FF 또는 0xE0000x10FFFF 범위일 때). c-char가 단일 UTF-32 코드 단위로 표현될 수 없다면 그 프로그램은 ill-formed예요. (C23부터)

  7. 와이드 문자 상수. 예: L'β', L'貓'. 타입은 wchar_t이고, 값은 c-char가 실행 와이드 문자 집합에서 가지는 값과 같아요(즉 mbtowc가 만들어 내는 값). c-char를 표현할 수 없거나 두 개 이상의 와이드 문자로 매핑된다면(예: wchar_t가 16비트인 Windows에서 비-BMP 값), 값은 구현 정의예요.

  8. 멀티 문자 상수. 예: 'AB'. 타입은 int이고 값은 구현 정의예요.

  9. 와이드 멀티 문자 상수. 예: L'AB'. 타입은 wchar_t이고 값은 구현 정의예요.

  10. 16비트 멀티 문자 상수. 예: u'CD'. 타입은 char16_t이고 값은 구현 정의예요.

  11. 32비트 멀티 문자 상수. 예: U'XY'. 타입은 char32_t이고 값은 구현 정의예요.

주의할 점(Notes)

멀티 문자 상수는 C가 B 프로그래밍 언어에서 물려받은 것이에요. C 표준이 명시하지는 않았지만, 대부분의 컴파일러(MSVC는 예외)는 B에서 정의된 대로 멀티 문자 상수를 구현해요. 상수 안 각 문자의 값이 결과 정수의 연속된 바이트를 초기화하는데, 빅엔디안·0 패딩·오른쪽 정렬 순서예요. 예를 들어 '\1'의 값은 0x00000001이고, '\1\2\3\4'의 값은 0x01020304예요.

C++에서는 인코딩할 수 있는 일반 문자 리터럴이 int가 아니라 char 타입이에요.

정수 상수와 달리, char가 부호 있는 타입이라면 문자 상수는 음수 값을 가질 수 있어요. 그런 구현에서 '\xFF'는 값이 -1int예요.

#if#elif의 제어 표현식에서 쓰일 때, 문자 상수는 소스 문자 집합, 실행 문자 집합, 또는 다른 구현 정의 문자 집합으로 해석될 수 있어요.

16/32비트 멀티 문자 상수는 널리 지원되지 않고 C23에서 제거됐어요. 몇몇 흔한 구현(예: clang)은 아예 받아들이지 않아요.

예제

여기서는 각 종류의 문자 상수가 가지는 실제 값을 확인해 볼게요.

#include <stddef.h>
#include <stdio.h>
#include <uchar.h>

int main (void)
{
    printf("constant value     \n");
    printf("-------- ----------\n");

    // integer character constants,
    int c1='a'; printf("'a':\t %#010x\n", c1);
    int c2='🍌'; printf("'🍌':\t %#010x\n\n", c2); // implementation-defined

    // multicharacter constant
    int c3='ab'; printf("'ab':\t %#010x\n\n", c3); // implementation-defined

    // 16-bit wide character constants
    char16_t uc1 = u'a'; printf("'a':\t %#010x\n", (int)uc1);
    char16_t uc2 = u'¢'; printf("'¢':\t %#010x\n", (int)uc2);
    char16_t uc3 = u'猫'; printf("'猫':\t %#010x\n", (int)uc3);
    // implementation-defined (🍌 maps to two 16-bit characters)
    char16_t uc4 = u'🍌'; printf("'🍌':\t %#010x\n\n", (int)uc4);

    // 32-bit wide character constants
    char32_t Uc1 = U'a'; printf("'a':\t %#010x\n", (int)Uc1);
    char32_t Uc2 = U'¢'; printf("'¢':\t %#010x\n", (int)Uc2);
    char32_t Uc3 = U'猫'; printf("'猫':\t %#010x\n", (int)Uc3);
    char32_t Uc4 = U'🍌'; printf("'🍌':\t %#010x\n\n", (int)Uc4);

    // wide character constants
    wchar_t wc1 = L'a'; printf("'a':\t %#010x\n", (int)wc1);
    wchar_t wc2 = L'¢'; printf("'¢':\t %#010x\n", (int)wc2);
    wchar_t wc3 = L'猫'; printf("'猫':\t %#010x\n", (int)wc3);
    wchar_t wc4 = L'🍌'; printf("'🍌':\t %#010x\n\n", (int)wc4);
}

가능한 출력:

constant value     
-------- ----------
'a':	 0x00000061
'🍌':	 0xf09f8d8c

'ab':	 0x00006162

'a':	 0x00000061
'¢':	 0x000000a2
'猫':	 0x0000732b
'🍌':	 0x0000df4c

'a':	 0x00000061
'¢':	 0x000000a2
'猫':	 0x0000732b
'🍌':	 0x0001f34c

'a':	 0x00000061
'¢':	 0x000000a2
'猫':	 0x0000732b
'🍌':	 0x0001f34c

같은 '🍌'라도 16비트 상수에서는 두 개의 16비트 문자(0xdf4c)로 매핑되는 반면, 32비트 상수에서는 온전한 코드 포인트 0x1f34c를 담는다는 점이 눈에 띄어요.

더 알아보기

  • 문자열 리터럴(String literal): 문자의 연속을 담는 리터럴.
  • 이스케이프 시퀀스(Escape sequences): \n처럼 문자를 특별한 방식으로 표현하는 방법.