C 프로그래밍 : 유니 코드 용으로 프로그래밍하는 방법?


82

엄격한 유니 코드 프로그래밍을 수행하려면 어떤 전제 조건이 필요합니까?

이 내 코드는 사용하지 말아야 것을 의미합니까 char어디서나 종류와 그 기능을 처리 할 수있는 사용해야 wint_t하고 wchar_t?

그리고이 시나리오에서 멀티 바이트 문자 시퀀스가 ​​수행하는 역할은 무엇입니까?

답변:


21

이것은 "엄격한 유니 코드 프로그래밍"자체가 아니라 실제적인 경험에 관한 것입니다.

우리 회사에서 한 일은 IBM의 ICU 라이브러리를 중심으로 래퍼 라이브러리를 만드는 것이 었습니다. 래퍼 라이브러리에는 UTF-8 인터페이스가 있으며 ICU를 호출해야 할 때 UTF-16으로 변환됩니다. 우리의 경우 성능 저하에 대해 너무 걱정하지 않았습니다. 성능이 문제가되었을 때 우리는 자체 데이터 유형을 사용하여 UTF-16 인터페이스도 제공했습니다.

응용 프로그램은 일부 경우 특정 문제를 인식해야하지만 대부분있는 ​​그대로 (char 사용) 유지 될 수 있습니다. 예를 들어, strncpy () 대신 UTF-8 시퀀스를 자르지 않는 래퍼를 사용합니다. 우리의 경우에는 이것으로 충분하지만 문자 결합에 대한 검사도 고려할 수 있습니다. 또한 코드 포인트 수, 자소 수 등을 세는 래퍼도 있습니다.

다른 시스템과 인터페이스 할 때 때때로 사용자 지정 문자 구성을 수행해야하므로 응용 프로그램에 따라 유연성이 필요할 수 있습니다.

wchar_t를 사용하지 않습니다. ICU를 사용하면 이식성에서 예상치 못한 문제를 피할 수 있습니다 (물론 다른 예상치 못한 문제는 아님 :-).


2
유효한 UTF-8 바이트 시퀀스는 strncpy에 의해 절단 (잘림)되지 않습니다. 유효한 UTF-8 시퀀스는 0x00 바이트를 포함 할 수 없습니다 (물론 종료 널 바이트 제외).
Dan Molding

8
@Dan Moulding : strncpy (), 예를 들어 단일 중국어 문자 (3 바이트 일 수 있음)를 포함하는 문자열을 2 바이트 문자 배열로 만들면 잘못된 UTF-8 시퀀스가 ​​생성됩니다.
Hans van Eck

@Hans van Eck : 래퍼가 해당 단일 3 바이트 중국어 문자를 2 바이트 배열로 복사하는 경우이를 자르고 잘못된 시퀀스를 만들거나 정의되지 않은 동작을 갖게됩니다. 분명히 데이터를 복사하는 경우 대상은 충분히 커야합니다. 말할 필요도 없습니다. 내 요점은 strncpy올바르게 사용하면 UTF-8과 함께 사용하기에 완벽하게 안전하다는 것입니다.
Dan Molding 2011

5
@DanMoulding : 타겟 버퍼가 충분히 크다는 것을 알고 있다면 그냥 사용할 수 있습니다. strcpy(UTF-8과 함께 사용하는 것이 안전합니다). 사용하는 사람들 은 대상 버퍼가 충분히 큰지 알지 못strncpy 하기 때문에 그렇게 수 있으므로 복사 할 최대 바이트 수를 전달하려고합니다. 실제로 잘못된 UTF-8 시퀀스를 만들 수 있습니다.
Frerich Raabe 2013

41

C99 이하

C 표준 (C99)은 와이드 문자와 멀티 바이트 문자를 제공하지만 와이드 문자가 무엇을 보유 할 수 있는지에 대한 보장이 없기 때문에 값이 다소 제한됩니다. 주어진 구현에 대해 유용한 지원을 제공하지만 코드가 구현간에 이동할 수 있어야한다면 유용 할 것이라는 보장이 충분하지 않습니다.

결과적으로 Hans van Eck (ICU-International Components for Unicode-library를 둘러싼 래퍼를 작성하는 것)가 제안한 접근 방식은 IMO입니다.

UTF-8 인코딩에는 많은 장점이 있습니다. 그 중 하나는 데이터를 엉망으로 만들지 않으면 (예를 들어 잘라내어) UTF-8의 복잡성을 완전히 인식하지 못하는 함수로 복사 할 수 있다는 것입니다. 부호화. 이것은 wchar_t.

전체 유니 코드는 21 비트 형식입니다. 즉, 유니 코드는 U + 0000에서 U + 10FFFF까지의 코드 포인트를 예약합니다.

(UTF 유니 코드 변환 형식을 의미합니다 - 참조 UTF-8, UTF-16, UTF-32 형식에 대한 유용한 것들 중 하나는 유니 코드 )은 정보의 손실없이 세 가지 표현 사이의 변환을 할 수 있다는 것입니다. 각각은 다른 사람이 나타낼 수있는 모든 것을 나타낼 수 있습니다. UTF-8과 UTF-16은 모두 다중 바이트 형식입니다.

UTF-8은 멀티 바이트 형식으로 잘 알려져 있으며, 신중한 구조로 인해 문자열의 모든 지점에서 시작하여 안정적으로 문자열의 문자 시작을 찾을 수 있습니다. 1 바이트 문자는 상위 비트가 0으로 설정됩니다. 멀티 바이트 문자는 비트 패턴 110, 1110 또는 11110 (2 바이트, 3 바이트 또는 4 바이트 문자의 경우) 중 하나로 시작하는 첫 번째 문자를 가지며 후속 바이트는 항상 10으로 시작합니다. 연속 문자는 항상 범위 0x80 .. 0xBF. UTF-8 문자가 가능한 최소 형식으로 표시되어야한다는 규칙이 있습니다. 이러한 규칙의 한 가지 결과는 바이트 0xC0 및 0xC1 (또한 ​​0xF5..0xFF)이 유효한 UTF-8 데이터에 나타날 수 없다는 것입니다.

 U+0000 ..   U+007F  1 byte   0xxx xxxx
 U+0080 ..   U+07FF  2 bytes  110x xxxx   10xx xxxx
 U+0800 ..   U+FFFF  3 bytes  1110 xxxx   10xx xxxx   10xx xxxx
U+10000 .. U+10FFFF  4 bytes  1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx

원래는 유니 코드가 16 비트 코드 세트이고 모든 것이 16 비트 코드 공간에 맞기를 바랐습니다. 불행히도 현실 세계는 더 복잡하며 현재의 21 비트 인코딩으로 확장되어야했습니다.

따라서 UTF-16은 'Basic Multilingual Plane'에 대한 단일 단위 (16 비트 단어) 코드 세트입니다. 즉, 유니 코드 코드 포인트 U + 0000 .. U + FFFF가있는 문자를 의미하지만 두 단위 (32 비트)를 사용합니다. 이 범위를 벗어난 문자. 따라서 UTF-16 인코딩과 함께 작동하는 코드는 UTF-8과 마찬가지로 가변 너비 인코딩을 처리 할 수 ​​있어야합니다. 이중 단위 문자에 대한 코드를 서로 게이트라고합니다.

서로 게이트는 UTF-16에서 쌍을 이루는 코드 단위의 선행 및 후행 값으로 사용하도록 예약 된 두 가지 특수 유니 코드 값 범위의 코드 포인트입니다. 선행 (높음이라고도 함) 서로 게이트는 U + D800에서 U + DBFF까지이고 후행 (낮음) 서로 게이트는 U + DC00에서 U + DFFF까지입니다. 문자를 직접 나타내지 않고 한 쌍으로 만 나타 내기 때문에 서로 게이트라고합니다.

물론 UTF-32는 단일 저장소 단위로 모든 유니 코드 코드 포인트를 인코딩 할 수 있습니다. 계산에는 효율적이지만 저장에는 적합하지 않습니다.

ICU 및 유니 코드 웹 사이트 에서 더 많은 정보를 찾을 수 있습니다 .

C11 및 <uchar.h>

C11 표준은 규칙을 변경했지만 모든 구현이 지금 (2017 년 중반)에도 변경 사항을 따라 잡은 것은 아닙니다. C11 표준은 유니 코드 지원에 대한 변경 사항을 다음과 같이 요약합니다.

  • 유니 코드 문자 및 문자열 ( <uchar.h>) (원래 ISO / IEC TR 19769 : 2004에 지정됨)

다음은 기능에 대한 최소한의 개요입니다. 사양에는 다음이 포함됩니다.

6.4.3 범용 문자 이름

구문
universal-character-name :
    \u hex-quad
    \U hex-quad hex-quad
hex-quad :
    16 진수 숫자 16 진수 16 진수 16 진수 16 진수

7.28 유니 코드 유틸리티 <uchar.h>

헤더 <uchar.h>는 유니 코드 문자를 조작하기위한 유형과 함수를 선언합니다.

선언 된 유형은 mbstate_t(7.29.1에 설명 됨) 및 size_t(7.19에 설명 됨)입니다.

char16_t

16 비트 문자에 사용되는 부호없는 정수 유형이며, uint_least16_t7.20.1.2에 설명 된 것과 동일한 유형입니다 . 과

char32_t

32 비트 문자에 사용되는 부호없는 정수 유형이며 동일한 유형입니다 uint_least32_t(7.20.1.2에서도 설명 됨).

(상호 참조 번역 : <stddef.h>define size_t, <wchar.h>define mbstate_t, 및 <stdint.h>정의 uint_least16_tuint_least32_t.) <uchar.h>헤더는 또한 최소한의 (다시 시작 가능) 변환 함수 세트를 정의합니다.

  • mbrtoc16()
  • c16rtomb()
  • mbrtoc32()
  • c32rtomb()

\unnnn또는 \U00nnnnnn표기법을 사용하여 식별자에 유니 코드 문자를 사용할 수있는 규칙이 있습니다 . 식별자에서 이러한 문자에 대한 지원을 적극적으로 활성화해야 할 수 있습니다. 예를 들어, GCC는 -fextended-identifiers식별자에서이를 허용 해야 합니다.

macOS Sierra (10.12.5)는 하나의 플랫폼이지만 <uchar.h>.


3
나는 당신이 wchar_t여기에서 조금 짧은 친구와 팔고 있다고 생각합니다 . 이러한 유형은 C 라이브러리가 모든 인코딩 (비 유니 코드 인코딩 포함)의 텍스트를 처리 할 수 ​​있도록하기 위해 필수적입니다 . 넓은 문자 유형과 함수가 없으면 C 라이브러리는 지원되는 모든 인코딩에 대해 일련의 텍스트 처리 함수를 요구합니다 . 본문. 대신, 우리는이 행운 하나 이러한 기능 (원래 ASCII 것들을 계산하지 않음)의 설정을 : wcslen, wcstok,와 wprintf.
Dan Molding

1
프로그래머가해야 할 일은 C 라이브러리 문자 변환 함수 ( mbstowcs및 친구)를 사용하여 지원되는 인코딩을 wchar_t. 일단 wchar_t형식이 되면 프로그래머는 C 라이브러리가 제공하는 단일 집합의 넓은 텍스트 처리 함수를 사용할 수 있습니다. 좋은 C 라이브러리 구현은 대부분의 프로그래머가 필요로하는 거의 모든 인코딩을 지원합니다 (내 시스템 중 하나에서 221 개의 고유 인코딩에 액세스 할 수 있음).
Dan Molding

그것들이 유용 할만큼 충분히 넓을 지 여부에 관한 한 : 표준은 구현에서 wchar_t지원하는 모든 문자를 포함 할 수있을만큼 충분히 넓은 구현을 보장해야합니다 . 즉, (아마도 한 가지 주목할만한 예외가있을 수 있음) 대부분의 구현은 사용하는 프로그램이 wchar_t시스템에서 지원하는 모든 인코딩을 처리 할 수있을만큼 충분히 넓다는 것을 보장합니다 (Microsoft의 wchar_t구현은 모든 인코딩을 완전히 지원하지 않음을 의미하는 16 비트 너비이며, 가장 주목할만한 것은 다양한 UTF 인코딩이지만 규칙이 아닌 예외입니다).
Dan Molding

11

FAQ 는 풍부한 정보입니다. 해당 페이지와 Joel Spolsky의이 기사 사이 에서 좋은 출발을 할 수 있습니다.

나는 그 과정에서 한 가지 결론을 내렸다.

  • wchar_tWindows에서는 16 비트이지만 다른 플랫폼에서는 반드시 16 비트는 아닙니다. Windows에서 필요한 악이라고 생각하지만 다른 곳에서는 피할 수 있습니다. Windows에서 중요한 이유는 이름에 비 ASCII 문자가 포함 된 파일 (함수의 W 버전과 함께)을 사용해야하기 때문입니다.

  • wchar_t문자열 을받는 Windows API는 UTF-16 인코딩을 예상합니다. 이것은 UCS-2와 다릅니다. 서로 게이트 쌍을 기록해 둡니다. 이 테스트 페이지 에는 계몽 테스트가 있습니다.

  • 당신이 윈도우에있어 프로그래밍, 당신이 사용할 수없는 경우 fopen(), fread(), fwrite(), 등 그들은 단지 걸릴 이후 char *와 UTF-8 인코딩을 이해하지 않습니다. 휴대 성을 어렵게 만듭니다.


표준이 그렇게 말하고 있기 때문에 stdio f*와 친구들 char *모든 플랫폼 에서 작동 wcs*합니다. 대신 wchar_t를 사용하십시오.
고양이

7

엄격한 유니 코드 프로그래밍을 수행하려면 :

  • 만있는 문자열 API를 사용하는 유니 코드 인식 ( NOT strlen , strcpy... 그러나 그들의 WideString으로 대응 wstrlen, wsstrcpy...)
  • 텍스트 블록을 다룰 때는 유니 코드 문자 (utf-7, utf-8, utf-16, ucs-2, ...)를 손실없이 저장할 수있는 인코딩을 사용하십시오.
  • OS 기본 문자 집합이 유니 코드와 호환되는지 확인합니다 (예 : utf-8).
  • 유니 코드와 호환되는 글꼴 사용 (예 : arial_unicode)

멀티 바이트 문자 시퀀스는 UTF-16 인코딩 (와 함께 일반적으로 사용되는 인코딩)보다 이전의 인코딩이며 wchar_t나에게는 오히려 Windows 전용 인 것 같습니다.

나는 들어 본 적이 없다 wint_t.


wint_t는 wchar_t와 마찬가지로 <wchar.h>에 정의 된 유형입니다. 와이드 문자에 대해서는 int가 'char'에 대해 갖는 것과 동일한 역할을합니다. 와이드 문자 값 또는 WEOF를 보유 할 수 있습니다.
Jonathan Leffler

3

가장 중요한 것은 항상 텍스트와 이진 데이터를 명확하게 구분하는 것입니다 . 의 모델에 따라 시도 파이썬 3.x를 strbytes 또는 SQL TEXT대를 BLOB.

불행히도 C char는 "ASCII 문자"와 int_least8_t. 다음과 같이 할 수 있습니다.

typedef char UTF8; // for code units of UTF-8 strings
typedef unsigned char BYTE; // for binary data

UTF-16 및 UTF-32 코드 단위에 대한 typedef도 원할 수 있지만 인코딩이 wchar_t정의되지 않았기 때문에 더 복잡합니다 . 전처리기만 있으면됩니다 #if. C 및 C ++ 0x의 유용한 매크로는 다음과 같습니다.

  • __STDC_UTF_16__— 정의 된 경우 유형 _Char16_t이 존재하며 UTF-16입니다.
  • __STDC_UTF_32__— 정의 된 경우 유형 _Char32_t이 존재하며 UTF-32입니다.
  • __STDC_ISO_10646__— 정의 된 경우 wchar_tUTF-32입니다.
  • _WIN32— Windows에서는 wchar_t표준을 위반하더라도 UTF-16입니다.
  • WCHAR_MAX—의 크기를 결정하는 데 사용할 수 wchar_t있지만 OS에서 유니 코드를 나타내는 데 사용하는지 여부는 확인할 수 없습니다.

이것은 내 코드가 어디에서나 char 유형을 사용하지 않아야하고 wint_t 및 wchar_t를 처리 할 수있는 함수를 사용해야 함을 의미합니까?

또한보십시오:

아니요. UTF-8은 char*문자열 을 사용하는 완벽하게 유효한 유니 코드 인코딩입니다 . 그것은 장점이 프로그램이 비 ASCII 바이트에 투명 중일 경우 (예를 들어,에 작용 계산기 끝나는 라인 \r\n하지만 변하지 다른 문자 통과), 당신은 전혀 변경하지해야합니다을!

UTF-8을 사용하는 경우 char= 문자 (예 : toupper루프에서 호출하지 않음 ) 또는 char= 화면 열 (예 : 텍스트 줄 바꿈) 이라는 모든 가정을 변경해야합니다 .

UTF-32를 사용하면 고정 너비 문자의 단순성을 갖게됩니다 (고정 너비 graphemes 는 아니지만 모든 문자열의 유형을 변경해야 함).

당신이 UTF-16와 함께 갈 경우, 고정 폭 문자의 가정을 모두 폐기해야 하고 이 단일 바이트 인코딩에서 가장 어려운 업그레이드 경로하게 8 비트 코드 단위의 가정을,.

크로스 플랫폼이 아니기 때문에 적극적으로 피하는 것이 좋습니다 wchar_t. 때로는 UTF-32이고 때로는 UTF-16이며 때로는 유니 코드 이전의 동아시아 인코딩입니다. 나는 사용하는 것이 좋습니다typedefs

더욱 중요한 것은, TCHAR .


나는 그것이 전혀 불행하다고 생각하지 않습니다-char는 int입니다. 그것은 이점입니다. 리터럴 문자 상수를 사용하는 것은 한 가지 용도로 떠 오릅니다. 그리고 a를 취하는 함수 char *const char *마지막으로 통과하면 문제가 발생할 수 있습니다 (하지만 나는 이것에 대해 모호하고 어떤 기능을 소금 한 꼬집으로 가져 가십시오). 다른 언어에 비해 더 복잡하다고해서 디자인이 나쁘다는 의미는 아닙니다.
Pryftan

2

나는 표준 라이브러리 구현을 신뢰하지 않을 것입니다. 고유 한 유니 코드 유형을 롤링하십시오.

#include <windows.h>

typedef unsigned char utf8_t;
typedef unsigned short utf16_t;
typedef unsigned long utf32_t;

int main ( int argc, char *argv[] )
{
  int msgBoxId;
  utf16_t lpText[] = { 0x03B1, 0x0009, 0x03B2, 0x0009, 0x03B3, 0x0009, 0x03B4, 0x0000 };
  utf16_t lpCaption[] = L"Greek Characters";
  unsigned int uType = MB_OK;
  msgBoxId = MessageBoxW( NULL, lpText, lpCaption, uType );
  return 0;
}

2

기본적으로 메모리의 문자열 wchar_t을 char 대신 배열 로 처리하고 싶습니다 . 어떤 종류의 I / O (파일 읽기 / 쓰기 등)를 수행 할 때 구현하기에 충분히 간단한 UTF-8 (가장 일반적인 인코딩)을 사용하여 인코딩 / 디코딩 할 수 있습니다. RFC를 Google로 검색하세요. 따라서 메모리 내 어떤 것도 멀티 바이트가 아니어야합니다. 하나 wchar_t는 하나의 문자를 나타냅니다. 그러나 직렬화에 관해서는 일부 문자가 여러 바이트로 표시되는 UTF-8과 같은 것으로 인코딩해야 할 때입니다.

또한 strcmp넓은 문자열에 대해 새 버전 등 을 작성해야 하지만 이는 큰 문제가 아닙니다. 가장 큰 문제는 문자 배열 만 허용하는 라이브러리 / 기존 코드와의 상호 운용성입니다.

그리고 sizeof(wchar_t)(올바르게하고 싶다면 4 바이트가 필요 합니다) 필요한 경우 typedef/ macrohacks 를 사용하여 항상 더 큰 크기로 재정의 할 수 있습니다 .


1

내가 아는 바에 따르면 wchar_t는 구현에 따라 다릅니다 (이 위키 기사 에서 볼 수 있듯이 ). 그리고 그것은 유니 코드가 아닙니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.