logo

English

이곳의 프로그래밍관련 정보와 소스는 마음대로 활용하셔도 좋습니다. 다만 쓰시기 전에 통보 정도는 해주시는 것이 예의 일것 같습니다. 질문이나 오류 수정은 siseong@gmail.com 으로 주세요. 감사합니다.

wchar_t에 대하여

by digipine posted Nov 01, 2017
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print

* wide character (wchar_t)

wide character란 1바이트가 아닌 그 이상의 바이트를 차지하는 정수에 저장되는 문자 코드를 의미한다. 현재 wide character로 표현되는 문자 코드는 지구상에 유니코드 밖에 없다. 유니코드를 wide character로 표현하는 방법은 UCS-2, UTF-16, UTF-32의 세가지가 있을 수 있다. 앞의 두 가지는 2바이트 단위이고 마지막은 4바이트 단위로 저장된다.

C 표준은 wide character를 표현하기 위해 wchar_t 데이터 형을 정의하고 있다. wchar_t의 정확한 크기는 구현에 따라 다른데 MSVC에서는 내부적으로 unsigned short형을 사용하며, gcc는 int 형을 사용한다. 따라서 MSVC의 wchar_t는 UCS-2를 나타내며(MS의 문서에 따르면 UTF-16은 아닌 듯 하다.), gcc의 wchar_t는 UCS-4(엄밀히 UTF-32)를 나타낸다. (하지만, 실제로 16비트 이상의 부분도 지원하는지 불명확하다.)

MS 윈도우즈는 C 표준과 별도로 자체 API상에서 유니코드를 지원하는데, 이 때는 WCHAR를 사용한다. WCHAR는 unsigned short로서 MS의 컴파일러에서의 wchar_t와 일치하도록 하였다.

C표준은 (최신 C99마저도) wchar_t의 형식을 통일하는데 실패하였다. wchar_t는 시스템에 따라서 UCS-2일 수도 있고 UTF-32일 수도 있다. gcc에 -fshort-wchar 옵션을 주면 wchar_t가 unsigned short를 사용하도록 할 수 있다. 이렇게 컴파일러 옵션으로 변경했을 경우에 표준 C 라이브러리들이 거기에 맞게 동작할지도 알 수 없다. (즉, 안 바꾸는 편이 좋다.) 따라서 우리는 wchar_t가 UCS-2일지 UTF-32일지 단정할 수 없는 상황이다. (심지어 UTF-16인지도 모른다!)

응용 프로그램의 입장에서는 wchar_t에 대해서 C 라이브러리의 함수에 그냥 의존하는 수 밖에 없다. (어떠한 전제를 하기 보다는) 다만, 꼭 필요한 경우 sizeof(wchar_t)의 값에 따라서 다르게 동작하는 수 밖에 없을 것 같다.

* wint_t

C표준은 wchar_t 외에 wint_t를 같이 정의하고 있다. wint_t가 필요한 이유에 대해서는 fgetc()와 같은 함수들을 생각하면 된다. 이 함수는 파일에서 1바이트를 읽어서 int 형으로 리턴한다. 왜 char가 아니고 int일까? 왜냐하면 이들 함수는 읽은 바이트 뿐 아니라 에러 값도 표현할 수 있어야 하기 때문이다. 즉, -1은 에러(다시 말해 EOF)를 나타내고 0 이상의 값은 정상적으로 읽어들인 바이트를 의미한다.

이런 용법은 wchar_t에도 적용된다. 예컨데 wide character 버전의 fgetc()가 있다면, wchar_t를 리턴하기 보다는 wchar_t 외의 에러 값도 표현할 수 있는 타입을 리턴해야 할 것이다. 이때 사용하는 것이 wint_t이다. 그냥 int를 써도 무방할 수 있겠지만 int가 wchar_t와 똑같이 16비트일 경우가 있을 수 있으므로 wint_t를 사용해야 한다. (이때, wint_t는 32비트 이상이어야 한다.) wchar_t가 32비트라고 해도 UCS-4가 31비트까지만 사용하기 때문에 wint_t가 같은 32비트 부호있는 정수라면 음수 값으로 충분히 에러를 나타낼 수 있다.

즉, wint_t는 한 개의 유니코드 값을 주고 받기 위해서 함수 인수나 리턴 값으로 사용되며, wint_t의 배열로서 문자열을 나타내지는 않는다.


* 문자열 상수(string literal)의 표현

C/C++ 소스 파일은 보통 US-ASCII나 ISO-2022에 따르는 MBCS로 작성된다. (최근에는 UTF-8로도 작성한다.) 그러나 C 언어 자체는 ASCII만을 사용하도록 하고 있으며 최근의 개정판인 C99에서는 더 나아가 ISO 646이라는 ASCII 중에서도 일부만 사용하도록 하였다. 물론 대부분의 컴파일러가 전체 ASCII를 사용할 수 있지만, ASCII를 지원하지 않는 예외적인 시스템을 위해서 그렇게 정의한 것이다. (최신 gcc는 컴파일러 옵션으로 ASCII가 아닌 ISO 646을 사용하도록 선택할 수 있다. 물론 기본값은 전통적인 ASCII이다.)

C 언어에서 문자열 상수(string literal)은 다음과 같이 나타낼 수 있다.

char text[] = "문자열";

다행히 대부분의 컴파일러는 문자열 상수 안에 ASCII를 벗어난 코드가 있어도 허용하고 있다. C 컴파일러는 문자열 상수을 소스 코드 파일에 "있는 그대로" 해석한다. 즉, 소스 코드 자체가 어떤 문자 코드로 작성된 것인지 인식할 수도 없고 지정할 수도 없다. 파일 안에 기록된 바이트 그대로 프로그램안에 '복사'될 뿐이다. 따라서 프로그램에 컴파일된 문자열 데이터는 작성한 소스 파일의 인코딩 그대로 들어가는 것이다.

사실 소스 코드 상의 문자열에 영문(US-ASCII) 외의 문자를 넣는 것은 국제적인 프로젝트에서 바람직하지 않다. 소스 코드를 보는 사람의 국적에 따라서 파일이 다르게 보일 수 있기 때문이다. (UTF-8이 아닌 이상) 그렇다면 UTF-8을 사용하면 되는 것인가? 이것이 현재로선 가장 좋은 방법이다.

만약, 소스 파일의 인코딩과 다른 인코딩의 문자열을 표현하려면 다음과 같이 코드 하나하나 숫자로 풀어서 쓸 수도 있을 것이다.

char text[] = { 0xb9, 0xae, 0xc0, 0xda, 0xbf, 0xad, 0x00 }; // EUC-KR
char text[] = { 0xeb, 0xac, 0xb8, 0xec, 0x9e, 0x90, 0xec, 0x97, 0xb4, 0x00 }; // UTF-8

위의 두 리소스는 모두 "문자열"을 나타낸다. 그러나 사실 위와 같이 풀어쓰기는 수동으로는 거의 불가능한 작업이다. 이것도 안 된다면 소스 코드 안에는 무조건 ASCII만을 사용하고 필요한 다른 인코딩의 문자열은 외부 파일에서 읽어들이는 수 밖에 없다.


* wchar_t 문자열 상수의 표현

앞에서 말한 문자열 상수는 char의 배열로서 표현하는 경우였다. 그러나 wchar_t 배열로 표현하는 경우에는 어떻게 할 것인가? 앞에서 본 대로 문자열 상수(string literal)이란 사실 소스 파일의 내용 그대로 복사하는 것이었다. 그렇다면 소스 파일에서 wchar_t을 표현할 수 있을까? 사실 위와 같은 방법으로는 불가능하다. 왜냐하면 앞에서도 말했지만 C/C++ 소스 파일은 wide character를 받아들이지 않기 때문이다. (ASCII와 호환되는 MBCS나 UTF-8만 가능하다.)

대신에 C 표준은 wchar_t를 위해서 문자열 상수의 변환을 지원하도록 하였다. 즉, 다음과 같이 MBCS나 UTF-8로 작성된 소스 파일 안에서 wide string literal을 표현할 수 있다.

wchar_t wtext[] = L"문자열";

이처럼 문자열 상수 앞에 "L"을 붙여서 wchar_t 타입의 문자열로 변환하도록 컴파일러에 요구할 수 있다. 다만 이 방법이 그리 유연하지 않다는 문제가 있다. 위의 문법 자체는 올바르며 gcc와 msvc와 같은 최신 컴파일러가 지원하고 있지만, gcc와 msvc의 해석은 전혀 다르다.

우선 msvc는 소스 파일 안에 있는 문자열을 시스템의 기본 로케일에 따라 인식한 후에 UCS-2로 변환한다. 즉, 컴파일 시점의 사용자 설정을 기준으로 변환한다는 것이다. 비록 이런 소스 코드가 국제화에 적합한 것은 아니겠지만, 프로그램 출력물 안에는 원하는 대로 UCS-2 문자열로 저장이 될 것이다.

반면 gcc는 3.4.0 이전에는 이런 변환을 제대로 지원하지 않고 있다. 많은 Unicode 자료에서 보면 위의 msvc 처럼 현재 사용자의 로케일에 따라 (LANG 환경 변수를 참조하여) 소스 파일의 문자열을 인식한다고 되어 있으나 실제로는 전혀 변환하지 않은 채 소스 파일의 각 바이트를 그대로 하나씩 wchar_t에 복사하고 있다. 즉, 소스 파일이 무조건 US-ASCII라고 전제하고 UTF-32로 변환한 것과 같다.

EUC-KR: b9 ae c0 da bf ad 00
정상적인 UTF-32: 0000bb38 0000c790 0000c5f4 00000000
gcc의 변환: 000000b9 000000ae 000000c0 000000da 000000bf 000000ad 00000000

맨 위의 EUC-KR은 "문자열"을 나타낸다. 하지만, gcc가 UTF-32로 변환한 결과는 원하는 것과는 다른 것이다. 단순히 1:1로 char 값을 wchar_t에 대응한 것임을 알 수 있다. 이런 현상은 환경 변수의 설정과는 무관하였다.

이 문제는 gcc 버그 리스트에 올라와 있지만, 개발자의 답변에 따르면 C 표준이 그렇게 정의하고 있기 때문에 컴파일러의 버그는 아니라는 입장이다. 오히려 문서에서 사용자의 로케일을 인식하여 변환해준다는 표현이 잘못된 것이라고 지적하였다. 결국 gcc 3.4.0 이전 버전에서는 소스 코드 안에서 ASCII 영역을 벗어난 wide string literal을 표현할 수 없는 것이 현실이다.

gcc 3.4.0에서는 전처리기 차원에서 문자열 인코딩을 변환해주는 옵션을 추가하였다. 이를 통하여 사용자가 원하는 대로 조절할 수 있을 것으로 보인다. 기본값은 소스 코드 상의 문자열은 UTF-8로 인식을 하고 wide string literal이 나오면 UTF-32로 변환하여 저장하도록 하였다. 그리고 소스 입력의 인코딩을 다른 것으로 지정할 수도 있도록 하였으며 저장될 때도 UTF-32가 아닌 UCS-2나 UTF-16으로 지정할 수도 있는 것으로 보인다. 입력은 시스템 로케일로 간주하고 출력은 UCS-2로 한정한 msvc 보다는 진일보한 것이라고 할 수 있다.

TAG •

List of Articles
No. Subject Author Date Views
23 [C/C++] 현재시간 구하기 digipine 2017.10.28 2212
22 C++에서 extern의 역할, 기능 digipine 2017.10.29 2656
21 STL MAP 예제로 공부하기 digipine 2017.10.29 5204
20 MD5 파일 변조 검사 관련 소스 (리눅스/Windows) digipine 2017.10.29 2613
19 brute-force 알고리즘을 이용한 패턴 위치 찾기 digipine 2017.10.29 1501
18 Solaris 10에 개발 Tool (gcc,vim,gdb) 설치 digipine 2017.10.29 1257
17 Solaris에서 pmap을 이용하여 백그라운드 프로세스 메모리 크기 구하기 digipine 2017.10.29 28598
16 Callback in C++ 와 Delegate 차이점 digipine 2017.11.01 2525
15 C를 이용한 객체지향 프로그래밍 digipine 2017.11.01 568
14 C 에서 Overloading 구현 digipine 2017.11.01 1790
» wchar_t에 대하여 digipine 2017.11.01 7343
12 Unix C/C++ Input and Output Function Reference digipine 2017.11.01 88073
11 [shared lib] so 동적 라이브러리 만들기와 사용법 - 리눅스 digipine 2017.11.01 6434
10 Introduce to Singly-linked List file digipine 2017.11.01 1288
9 Linux C 언어로 Shell 명령어 실행하기 digipine 2017.11.01 22587
8 [linux] zlib build 방법 digipine 2017.11.01 1483
7 [Linux] Pthread 사용법, Thread 동기화 총정리 digipine 2017.11.01 294049
6 make -j 옵션으로 컴파일 속도 최적화 하기 digipine 2017.11.01 2759
5 fopen 파일 열기 모드 옵션 정리 digipine 2017.11.02 3894
4 C++ 컴파일 오류(error): variable 'std::istringstream sstream' has initializer but incomplete type digipine 2017.11.02 21077
Board Pagination Prev 1 2 Next
/ 2