문자 상수(Character constant) — 'a'부터 L'貓'까지

문자 상수(Character constant) — 'a'부터 L'貓'까지

코드에서 'a'처럼 따옴표로 감싼 문자를 쓰곤 하죠. 이것이 **문자 상수(character constant)**예요. 그런데 C의 문자 상수는 한 가지 형태만 있는 게 아니에요. u8'…', u'…', U'…', L'…' 같은 각종 프리픽스가 붙을 수 있고, 각 형태는 서로 다른 타입과 인코딩 규칙을 가져요. 이 문서는 문자 상수의 다양한 형태와 그 타입·값 규칙을 정리해요.

출처: cppreference

본문

문법(Syntax)

'c-char'                  (1)
u8'c-char'                (2)  (since C23)
u'c-char'                 (3)  (since C11)
U'c-char'                 (4)  (since C11)
L'c-char'                 (5)
'c-char-sequence'         (6)
L'c-char-sequence'        (7)
u'c-char-sequence'        (8)  (since C11, removed in C23)
U'c-char-sequence'        (9)  (since C11, removed in 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가 단일 UTF-8 코드 단위로 표현 가능할 때(즉 0x0–0x7F 범위) ISO 10646 코드 포인트 값과 같아요. 단일 UTF-8 코드 단위로 표현할 수 없으면 프로그램은 ill-formed예요.

  3. 16비트 와이드 문자 상수, 예: u'貓' ( u'🍌'은 아님 — u'\U0001f34c'). char16_t 타입이고, mbrtoc16이 만드는 16비트 인코딩(보통 UTF-16)에서 c-char의 값과 같아요. 표현할 수 없거나 하나 이상의 16비트 문자로 매핑되면 값은 구현 정의예요. C23까지 동작. (C23부터는 아래 3) 항 참고)

  4. 32비트 와이드 문자 상수, 예: U'貓' 또는 U'🍌'. char32_t 타입이고, 32비트 인코딩(보통 UTF-32)에서 c-char의 값과 같아요. 표현할 수 없거나 하나 이상의 32비트 문자로 매핑되면 값은 구현 정의예요. C23까지 동작.

C23부터는 위 3)·4) 항이 다음과 같이 바뀌어요.

  1. UTF-16 문자 상수, 예: u'貓' ( u'🍌'은 아님 — u'\U0001f34c'). char16_t 타입이고, c-char가 단일 UTF-16 코드 단위로 표현 가능할 때(0x0–0xD7FF 또는 0xE000–0xFFFF) ISO 10646 코드 포인트 값과 같아요. 단일 UTF-16 코드 단위로 표현할 수 없으면 프로그램은 ill-formed예요.

  2. UTF-32 문자 상수, 예: U'貓' 또는 U'🍌'. char32_t 타입이고, c-char가 단일 UTF-32 코드 단위로 표현 가능할 때(0x0–0xD7FF 또는 0xE000–0x10FFFF) ISO 10646 코드 포인트 값과 같아요. 단일 UTF-32 코드 단위로 표현할 수 없으면 프로그램은 ill-formed예요.

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

  2. 다중 문자 상수(multicharacter constant), 예: 'AB'. int 타입이고 구현 정의 값을 가져요.

  3. 와이드 다중 문자 상수, 예: L'AB'. wchar_t 타입이고 구현 정의 값을 가져요.

  4. 16비트 다중 문자 상수, 예: u'CD'. char16_t 타입이고 구현 정의 값을 가져요.

  5. 32비트 다중 문자 상수, 예: U'XY'. char32_t 타입이고 구현 정의 값을 가져요.

참고(Notes)

다중 문자 상수는 C가 B 프로그래밍 언어에서 물려받았어요. C 표준이 규정하지는 않지만, 대부분의 컴파일러( MSVC는 주목할 만한 예외)는 B에서처럼 구현해요. 즉 상수 안 각 char의 값이 결과 정수의 연속된 바이트를, big-endian zero-padded right-adjusted 순서로 초기화해요. 예를 들어 '\1'0x00000001, '\1\2\3\4'0x01020304가 돼요.

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

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

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

16/32비트 다중 문자 상수는 널리 지원되지 않고 C23에서 제거됐어요. 몇몇 일반적인 구현(예: clang)은 아예 받아들이지 않아요.

예제(Example)

#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

u'🍌'(16비트)는 이미지 이모지가 하나의 16비트 문자로 표현이 안 돼서 값이 0x0000df4c처럼 조정된 값으로 나오는 반면, U'🍌'(32비트)는 그대로 0x0001f34c가 돼요.

더 알아보기

  • 문자열 리터럴에 대한 String literal 문서에서 프리픽스별 인코딩을 함께 다뤄요.
  • 이스케이프 시퀀스와 유니버설 문자 이름에 대한 문서를 참고해요.