문자 상수(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로 이뤄진 시퀀스예요.
-
단일 바이트 정수 문자 상수. 예:
'a','\n','\13'. 타입은int이고, 값은 c-char가char타입 값으로서 실행 문자 집합에서 가지는 표현을int로 매핑한 것과 같아요. c-char가 실행 문자 집합에서 단일 바이트로 표현될 수 없다면 값은 구현 정의예요. -
UTF-8 문자 상수. 예:
u8'a'. 타입은char8_t이고, 값은 c-char의 ISO 10646 코드 포인트 값과 같아요(코드 포인트 값이 단일 UTF-8 코드 단위로 표현 가능한 경우, 즉 c-char가 0x0~0x7F 범위일 때). c-char가 단일 UTF-8 코드 단위로 표현될 수 없다면 그 프로그램은 ill-formed예요. -
16비트 와이드 문자 상수. 예:
u'貓'(단,u'🍌'는 아님 —u'\U0001f34c'). 타입은char16_t이고, 값은 c-char가mbrtoc16이 만들어 내는 16비트 인코딩(보통 UTF-16)에서 가지는 값과 같아요. c-char를 표현할 수 없거나 두 개 이상의 16비트 문자로 매핑된다면 값은 구현 정의예요. -
32비트 와이드 문자 상수. 예:
U'貓'또는U'🍌'. 타입은char32_t이고, 값은 c-char가mbrtoc32가 만들어 내는 32비트 인코딩(보통 UTF-32)에서 가지는 값과 같아요. c-char를 표현할 수 없거나 두 개 이상의 32비트 문자로 매핑된다면 값은 구현 정의예요. (C23 이전) -
(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부터) -
(C23부터) UTF-32 문자 상수. 예:
U'貓'또는U'🍌'. 타입은char32_t이고, 값은 c-char의 ISO 10646 코드 포인트 값과 같아요(코드 포인트 값이 단일 UTF-32 코드 단위로 표현 가능한 경우, 즉 c-char가 0x00xD7FF 또는 0xE0000x10FFFF 범위일 때). c-char가 단일 UTF-32 코드 단위로 표현될 수 없다면 그 프로그램은 ill-formed예요. (C23부터) -
와이드 문자 상수. 예:
L'β',L'貓'. 타입은wchar_t이고, 값은 c-char가 실행 와이드 문자 집합에서 가지는 값과 같아요(즉mbtowc가 만들어 내는 값). c-char를 표현할 수 없거나 두 개 이상의 와이드 문자로 매핑된다면(예:wchar_t가 16비트인 Windows에서 비-BMP 값), 값은 구현 정의예요. -
멀티 문자 상수. 예:
'AB'. 타입은int이고 값은 구현 정의예요. -
와이드 멀티 문자 상수. 예:
L'AB'. 타입은wchar_t이고 값은 구현 정의예요. -
16비트 멀티 문자 상수. 예:
u'CD'. 타입은char16_t이고 값은 구현 정의예요. -
32비트 멀티 문자 상수. 예:
U'XY'. 타입은char32_t이고 값은 구현 정의예요.
주의할 점(Notes)
멀티 문자 상수는 C가 B 프로그래밍 언어에서 물려받은 것이에요. C 표준이 명시하지는 않았지만, 대부분의 컴파일러(MSVC는 예외)는 B에서 정의된 대로 멀티 문자 상수를 구현해요. 상수 안 각 문자의 값이 결과 정수의 연속된 바이트를 초기화하는데, 빅엔디안·0 패딩·오른쪽 정렬 순서예요. 예를 들어 '\1'의 값은 0x00000001이고, '\1\2\3\4'의 값은 0x01020304예요.
C++에서는 인코딩할 수 있는 일반 문자 리터럴이 int가 아니라 char 타입이에요.
정수 상수와 달리, char가 부호 있는 타입이라면 문자 상수는 음수 값을 가질 수 있어요. 그런 구현에서 '\xFF'는 값이 -1인 int예요.
#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처럼 문자를 특별한 방식으로 표현하는 방법.