이 글은 UTF-8 Everywhere을 한국어로 번역한 글입니다.
AI 모델 claude 3.5 sonnet을 사용하여 변역하였으여, 부정확한 내용이 있을 수 있습니다.
선언문
이 문서에는 특수 문자가 포함되어 있습니다. 적절한 렌더링 지원이 없으면 물음표, 상자 또는 다른 기호가 표시될 수 있습니다.
우리의 목표는 UTF-8 인코딩의 사용과 지원을 촉진하고, 메모리나 디스크에 텍스트 문자열을 저장하거나 통신 및 기타 모든 용도에 있어 기본 선택이 되어야 한다는 것을 설득하는 것입니다. 우리는 이러한 접근 방식이 성능을 향상시키고, 소프트웨어의 복잡성을 줄이며, 많은 유니코드 관련 버그를 예방하는 데 도움이 된다고 믿습니다. 우리는 유니코드의 다른 인코딩(또는 일반적인 텍스트)이 최적화의 희귀한 엣지 케이스에 속하며 주류 사용자들이 피해야 한다고 제안합니다.
특히, 우리는 매우 인기 있는 UTF-16 인코딩(Windows 세계에서는 종종 'widechar' 또는 단순히 'Unicode'로 잘못 불림)이 ICU와 같은 특수 텍스트 처리 라이브러리를 제외하고는 라이브러리 API에서 설 자리가 없다고 믿습니다.
또한 이 문서는 역사적인 이유와 API의 기본 UTF-8 지원 부족으로 인해 이 표준이 덜 인기 있음에도 불구하고, Windows 애플리케이션에서 내부 문자열 표현으로 UTF-8을 선택할 것을 권장합니다. 우리는 이러한 플랫폼에서도 다음의 주장들이 기본 지원 부족을 상쇄한다고 믿습니다. 또한 'ANSI 코드페이지'가 무엇이고 무엇을 위해 사용되었는지 영원히 잊을 것을 권장합니다. 모든 텍스트 문자열에서 어떤 언어든 혼합할 수 있는 것은 사용자의 권리입니다.
업계 전반에 걸쳐 많은 지역화 관련 버그들이 프로그래머들의 유니코드 지식 부족 때문이라고 비난받아 왔습니다. 하지만 우리는 텍스트를 전문으로 하지 않는 애플리케이션의 경우, 인프라가 프로그램이 인코딩 문제를 인식하지 않고도 작동할 수 있도록 해야 하며, 또 그렇게 할 수 있다고 믿습니다. 예를 들어, 파일 복사 유틸리티는 비영어권 파일 이름을 지원하기 위해 다르게 작성될 필요가 없습니다. 이 선언문에서는 문자열 내부가 무엇인지 신경 쓰지 않고, 유니코드의 모든 복잡성을 파고들지 않으려는 프로그래머가 무엇을 해야 하는지도 설명할 것입니다.
더욱이, 우리는 유니코드 코드 포인트를 세거나 반복하는 것이 텍스트 처리 시나리오에서 특별히 중요한 작업으로 간주되어서는 안 된다고 제안하고 싶습니다. 많은 개발자들이 코드 포인트를 ASCII 문자의 일종의 후계자로 잘못 보고 있습니다. 이는 Python의 문자열 O(1) 코드 포인트 접근과 같은 소프트웨어 설계 결정으로 이어졌습니다. 하지만 실제로는 유니코드가 본질적으로 더 복잡하며 _유니코드 문자_라는 것의 보편적인 정의가 없습니다. 우리는 유니코드 코드 포인트를 유니코드 자소 클러스터, 코드 단위 또는 심지어 언어의 단어보다 선호할 특별한 이유를 보지 못합니다. 반면에, UTF-8 코드 단위(바이트)를 텍스트의 기본 단위로 보는 것은 일반적으로 사용되는 텍스트 데이터 형식을 파싱하는 것과 같은 많은 작업에 특히 유용해 보입니다. 이는 이 인코딩의 특별한 기능 때문입니다. 자소, 코드 단위, 코드 포인트 및 기타 관련 유니코드 용어는 5장에서 설명됩니다. 인코딩된 텍스트 문자열에 대한 연산은 7장에서 논의됩니다.
1988년, Joseph D. Becker가 첫 유니코드 초안 제안서를 발표했습니다. 그의 설계의 기초에는 문자당 16비트면 충분할 것이라는 순진한 가정이 있었습니다. 1991년, 16비트로 제한된 코드 포인트를 가진 유니코드 표준의 첫 버전이 발표되었습니다. 이후 몇 년 동안 많은 시스템이 유니코드 지원을 추가하고 UCS-2 인코딩으로 전환했습니다. Qt 프레임워크(1992), Windows NT 3.1(1993), Java(1995)와 같은 새로운 기술에 특히 매력적이었습니다.
하지만 문자당 16비트로는 유니코드에 충분하지 않다는 것이 곧 발견되었습니다. 1996년, 기존 시스템이 16비트가 아닌 문자와도 작동할 수 있도록 UTF-16 인코딩이 만들어졌습니다. 이는 사실상 고정 폭 인코딩이라는, 처음에 16비트 인코딩을 선택한 근거를 무효화했습니다. 현재 유니코드는 109,449개의 문자를 포함하고 있으며, 그 중 약 74,500개가 CJK 한자입니다.
[이미지 설명: 큰 인코딩 포스터 앞에서 인코딩 게임을 하는 어린 아이]
나고야 시 과학관. 사진: Vadim Zlotnik.
Microsoft는 종종 'Unicode'와 'widechar'를 'UCS-2'와 'UTF-16' 모두의 동의어로 잘못 사용해 왔습니다. 더욱이, UTF-8을 좁은 문자열 WinAPI의 인코딩으로 설정할 수 없기 때문에, UNICODE 정의로 코드를 컴파일해야 합니다. Windows C++ 프로그래머들은 유니코드는 'widechars'로 해야 한다고 교육받습니다(또는 더 나쁘게는 - 컴파일러 설정에 따라 달라지는 TCHAR을 사용하여, 프로그래머가 모든 유니코드 코드 포인트 지원을 옵트아웃할 수 있게 합니다). 이런 혼란의 결과로, 많은 Windows 프로그래머들은 현재 텍스트에 대해 무엇이 올바른 일인지 매우 혼란스러워하고 있습니다.
동시에, Linux와 웹 세계에서는 UTF-8이 유니코드에 사용할 최고의 인코딩이라는 묵시적 합의가 있습니다. 영어와 컴퓨터 언어(C++, HTML, XML 등)에 대해 더 짧은 표현을 제공함에도 불구하고, 일반적으로 사용되는 문자 집합에 대해 UTF-16보다 덜 효율적인 경우는 거의 없습니다.
-
UTF-8과 UTF-16 인코딩 모두에서 코드 포인트는 최대 4바이트까지 차지할 수 있습니다.
-
UTF-8은 엔디안에 독립적입니다. UTF-16은 UTF-16LE와 UTF-16BE(각각 다른 바이트 순서용) 두 가지 형태로 제공됩니다. 여기서는 이들을 통칭하여 UTF-16이라고 합니다.
-
Widechar는 일부 플랫폼에서는 2바이트, 다른 플랫폼에서는 4바이트 크기입니다.
-
UTF-8과 UTF-32는 사전식으로 정렬할 때 동일한 순서를 산출합니다. UTF-16은 그렇지 않습니다.
-
UTF-8은 영어 문자와 다른 ASCII 문자(문자당 1바이트)에 대해 효율성을 선호하는 반면, UTF-16은 여러 아시아 문자 집합(UTF-8의 3바이트 대신 2바이트)에 대해 선호합니다. 이것이 웹 세계에서 영어 HTML/XML 태그가 모든 언어의 텍스트와 섞여 있는 경우 UTF-8이 선호되는 선택이 된 이유입니다. 키릴 문자, 히브리어 및 기타 여러 인기 있는 유니코드 블록은 UTF-16과 UTF-8 모두에서 2바이트입니다.
-
UTF-16은 Windows 패키지 프로그램 자체에서도 고정 폭 인코딩으로 잘못 사용되곤 합니다: Vista 이전의 일반 Windows 편집 컨트롤에서는 UTF-16에서 4바이트를 차지하는 문자를 삭제하는 데 백스페이스를 두 번 눌러야 합니다. Windows 7에서는 콘솔이 사용되는 폰트에 관계없이 그러한 문자를 두 개의 잘못된 문자로 표시합니다.
-
Windows용 많은 서드파티 라이브러리는 유니코드를 지원하지 않습니다: 좁은 문자열 매개변수를 받아들이고 이를 ANSI API로 전달합니다. 파일 이름의 경우에도 때때로 그렇습니다. 일반적인 경우, 문자열이 어떤 ANSI 코드 페이지에서도 완전히 표현될 수 없기 때문에(유니코드 블록의 혼합으로부터 문자를 포함하는 경우) 이를 해결하는 것은 불가능합니다. Windows 프로그래머들이 파일 이름에 대해 일반적으로 하는 것은 파일의 8.3 경로를 얻어(이미 존재하는 경우) 그것을 그러한 라이브러리에 제공하는 것입니다. 라이브러리가 존재하지 않는 파일을 생성해야 하는 경우에는 불가능합니다. 경로가 매우 길고 8.3 형식이 MAX_PATH보다 긴 경우에는 불가능합니다. OS 설정에서 짧은 이름 생성이 비활성화된 경우에는 불가능합니다.
-
C++에서는 UTF-8을 사용하는 것 외에는
std::exception::what()에서 유니코드를 반환할 방법이 없습니다.localeconv에 대해 유니코드를 지원할 방법도 UTF-8을 사용하는 것 외에는 없습니다. -
UTF-16은 오늘날에도 Windows 세계 밖에서도 인기가 있습니다. Qt, Java, C#, Python(CPython v3.3 참조 구현 이전, 아래 참조) 그리고 ICU - 이들 모두 내부 문자열 표현에 UTF-16을 사용합니다.
파일 복사 유틸리티로 돌아가 봅시다. UNIX 세계에서는 좁은 문자열이 거의 모든 곳에서 기본적으로 UTF-8로 간주됩니다. 그 덕분에 파일 복사 유틸리티의 작성자는 유니코드에 대해 신경 쓸 필요가 없습니다. 파일 이름 인자에 대해 ASCII 문자열로 테스트하면, 인자가 쿠키로 취급되기 때문에 모든 언어의 파일 이름에 대해 올바르게 작동할 것입니다. 파일 복사 유틸리티의 코드는 외국어를 지원하기 위해 전혀 변경할 필요가 없을 것입니다. fopen()은 유니코드를 원활하게 받아들일 것이고, argv도 마찬가지일 것입니다.
이제 UTF-16 기반 아키텍처인 Microsoft Windows에서는 어떻게 하는지 봅시다. 여러 다른 유니코드 블록(언어)이 혼합된 파일 이름을 받아들일 수 있는 파일 복사 유틸리티를 만들려면 여기서는 고급 트릭이 필요합니다. 먼저, 애플리케이션은 유니코드를 인식하도록 컴파일되어야 합니다. 이 경우에는 표준 C 매개변수를 가진 main() 함수를 가질 수 없습니다. 그러면 UTF-16로 인코딩된 argv를 받아들이게 됩니다. 좁은 텍스트를 염두에 두고 작성된 Windows 프로그램을 유니코드를 지원하도록 변환하려면, 깊이 리팩터링하고 모든 문자열 변수에 주의를 기울여야 합니다.
MSVC와 함께 제공되는 표준 라이브러리는 유니코드 지원 측면에서 제대로 구현되지 않았습니다. 좁은 문자열 매개변수를 직접 OS ANSI API로 전달합니다. 이를 재정의할 방법이 없습니다. std::locale을 변경하는 것은 작동하지 않습니다. C++의 표준 기능을 사용하여 MSVC에서 유니코드 이름을 가진 파일을 열 수 없습니다. 파일을 여는 표준 방법은 다음과 같습니다:
std::fstream fout("abc.txt");이를 해결하기 위한 적절한 방법은 넓은 문자열 매개변수를 받아들이는 Microsoft의 비표준 확장을 사용하는 것입니다.
Windows에서는 HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage\ACP 레지스트리 키가 단일 ANSI 코드페이지에서만 비ASCII 문자를 받을 수 있게 합니다. 구현되지 않은 65001 값은 아마도 Windows에서 쿠키 문제를 해결할 것입니다. Microsoft가 이 ACP 값의 지원을 구현한다면, 이는 Windows 플랫폼에서 UTF-8의 더 넓은 채택을 도울 것입니다.
Windows 프로그래머와 다중 플랫폼 라이브러리 공급자를 위해, 우리는 Windows에서 텍스트를 다루는 방법 섹션에서 텍스트 문자열을 다루고 더 나은 유니코드 지원을 위해 프로그램을 리팩터링하는 우리의 접근 방식을 더 논의합니다.
다음은 유니코드 표준에 따른 문자, 코드 포인트, 코드 단위 및 자소 클러스터에 대한 정의의 발췌문입니다. 더 자세한 설명은 표준의 관련 섹션을 참조하시기 바랍니다.
(번역자 메모: [§3.4, D10]는 3장 4절, 10번째 정의를 의미한다. §: 섹션(section), D: 정의(Definition))
Приве́т नमस्ते שָׁלוֹם
얼마나 많은 _문자_가 보이시나요?
-
코드 포인트 (Code Point) — 유니코드 코드 공간의 모든 숫자 값.[§3.4, D10] 예: U+3243F.
-
코드 단위 (Code Unit) — 인코딩된 텍스트의 단위를 표현할 수 있는 최소 비트 조합.[§3.9, D77] 예를 들어, UTF-8, UTF-16 및 UTF-32는 각각 8비트, 16비트 및 32비트 코드 단위를 사용합니다.
위의 코드 포인트는 UTF-8에서는 네 개의 코드 단위 'f0 b2 90 bf'로, UTF-16에서는 두 개의 코드 단위 'd889 dc3f'로, UTF-32에서는 하나의 코드 단위 '0003243f'로 인코딩됩니다. 이들은 단지 _비트 그룹_의 시퀀스일 뿐이며, 이들이 옥텟 지향 미디어에 어떻게 저장되는지는 특정 인코딩의 엔디안에 따라 달라집니다. 위의 UTF-16 코드 단위를 저장할 때, UTF-16BE에서는 'd8 89 dc 3f'로, UTF-16LE에서는 '89 d8 3f dc'로 변환됩니다.
-
추상 문자 (Abstract Character) — 텍스트 데이터의 조직, 제어 또는 표현에 사용되는 정보의 단위.[§3.4, D7] 표준은 §3.1에서 다음과 같이 말합니다:
유니코드 표준의 경우, [...] 레퍼토리는 본질적으로 열려 있습니다. 유니코드는 보편적 인코딩이기 때문에, 현재 알려져 있는지 여부에 관계없이 인코딩될 수 있는 모든 추상 문자는 잠재적으로 인코딩될 수 있는 후보입니다.
정의는 실제로 추상적입니다. 문자라고 생각할 수 있는 것이라면 무엇이든 추상 문자입니다. 예를 들어,
_tengwar letter ungwe_는 아직 유니코드로 표현할 수는 없지만 추상 문자입니다. -
인코딩된 문자, 코딩된 문자 — 코드 포인트와 추상 문자 사이의 매핑.[§3.4, D11] 예를 들어, U+1F428은 추상 문자 🐨 코알라를 나타내는 코딩된 문자입니다.
이 매핑은 전체적이지도, 단사적이지도, 전사적이지도 않습니다:
- 서로게이트, 비문자 및 할당되지 않은 코드 포인트는 추상 문자에 전혀 대응하지 않습니다.
- 일부 추상 문자는 서로 다른 코드 포인트로 인코딩될 수 있습니다; U+03A9 그리스 대문자 오메가와 U+2126 옴 기호는 모두 동일한 추상 문자 'Ω'에 대응하며, 동일하게 취급되어야 합니다.
- 일부 추상 문자는 단일 코드 포인트로 인코딩될 수 없습니다. 이들은 코딩된 문자의 _시퀀스_로 표현됩니다. 예를 들어, 추상 문자 ю́ _악센트가 있는 키릴 소문자 yu_를 표현하는 유일한 방법은 U+044E 키릴 소문자 yu 다음에 U+0301 결합 악센트 부호가 오는 시퀀스입니다.
더욱이, 일부 추상 문자의 경우, 단일 코딩된 문자 형식 외에도 여러 코드 포인트를 사용하는 표현이 존재합니다. 추상 문자 ǵ는 단일 코드 포인트 U+01F5 악센트가 있는 라틴 소문자 g로 코딩될 수 있거나, <U+0067 라틴 소문자 g, U+0301 결합 악센트 부호> 시퀀스로 코딩될 수 있습니다.
-
사용자 인식 문자 (User-Perceived Character) — 최종 사용자가 문자라고 생각하는 것. 이 개념은 언어에 따라 다릅니다. 예를 들어, 'ch'는 영어와 라틴어에서는 두 글자지만, 체코어와 슬로바키아어에서는 한 글자로 간주됩니다.
-
자소 클러스터 (Grapheme Cluster) — '함께 유지되어야 하는' 코딩된 문자의 시퀀스.[§2.11] 자소 클러스터는 언어에 독립적인 방식으로 사용자가 인식하는 문자의 개념을 근사화합니다. 예를 들어, 커서 이동과 선택에 사용됩니다.
-
글리프 (Glyph) — 폰트 내의 특정 모양. 폰트는 타입 디자이너가 설계한 글리프의 모음입니다. 코드 포인트 시퀀스를 지정된 폰트 내의 글리프 시퀀스로 변환하는 것은 텍스트 모양 지정 및 렌더링 엔진의 책임입니다. 이 변환을 위한 규칙은 복잡할 수 있고, 로케일에 따라 다를 수 있으며, 유니코드 표준의 범위를 벗어납니다.
'문자'는 위의 어느 것이나 가리킬 수 있습니다. 유니코드 표준은 이를 _코딩된 문자_의 동의어로 사용합니다.[§3.4] 프로그래밍 언어나 라이브러리 문서에서 '문자'라고 할 때는 일반적으로 코드 단위를 의미합니다. 최종 사용자에게 문자열의 문자 수를 물으면, 사용자가 인식하는 문자를 셀 것입니다. 프로그래머는 프로그래머의 유니코드 전문성 수준에 따라 문자를 코드 단위, 코드 포인트 또는 자소 클러스터로 셀 수 있습니다. 예를 들어, Twitter가 문자를 세는 방법이 있습니다. 우리의 의견으로는, '🐨' 문자열에 대해 길이 함수가 1을 반환할 필요는 없으며 여전히 유니코드를 준수하는 것으로 간주될 수 있습니다.
대부분의 유니코드 코드 포인트는 UTF-8과 UTF-16에서 같은 바이트 수를 차지합니다. 여기에는 러시아어, 히브리어, 그리스어 및 모든 비BMP 코드 포인트가 포함되며 두 인코딩 모두에서 2 또는 4바이트를 차지합니다. 구두점과 나머지 ASCII와 함께 라틴 문자는 UTF-16에서 더 많은 공간을 차지하는 반면, 일부 아시아 문자는 UTF-8에서 더 많은 공간을 차지합니다. 이론적으로, 아시아 프로그래머들이 문자당 메모리의 50%를 절약하는 UTF-16을 버리는 것에 반대할 수 있지 않을까요?
현실은 다음과 같습니다. 메모리를 절반으로 절약하는 것은 U+0800에서 U+FFFF 범위의 문자만 포함하는 인위적으로 구성된 예시에서만 사실입니다. 그러나 컴퓨터 간 텍스트 인터페이스가 다른 모든 텍스트 사용을 압도합니다. 여기에는 XML, HTTP, 파일시스템 경로 및 구성 파일이 포함되며, 이들은 거의 전적으로 ASCII 문자만 사용합니다. 실제로 해당 아시아 국가에서도 UTF-8이 매우 인기가 있습니다.
중국어 책의 전용 저장소의 경우, UTF-16은 여전히 합리적인 최적화로 사용될 수 있습니다. 그러한 저장소에서 텍스트를 검색하자마자, 나머지 세계와 호환되는 표준으로 변환되어야 합니다. 어느 경우든, 저장 공간이 중요하다면 무손실 압축이 사용될 것입니다. 이러한 경우, UTF-8과 UTF-16은 대략 같은 공간을 차지할 것입니다. 더욱이, '해당 언어에서는 글리프가 [라틴] 문자보다 더 많은 정보를 전달하므로 더 많은 공간을 차지하는 것이 정당화됩니다.' (Tronic, UTF-16 considered harmful).
간단한 실험 결과를 보여드리겠습니다. 첫 번째 열은 어떤 웹 페이지(2012-01-01에 일본 위키피디아에서 가져온 일본 기사)의 HTML 소스가 차지하는 공간을 보여줍니다. 두 번째 열은 마크업이 제거된 텍스트, 즉 '모두 선택, 복사, 일반 텍스트 파일에 붙여넣기'한 결과를 보여줍니다.
| 인코딩 | HTML 소스 (Δ UTF-8) | 순수 텍스트 (Δ UTF-8) |
|---|---|---|
| UTF-8 | 767 KB (0%) | 222 KB (0%) |
| UTF-16 | 1,186 KB (+55%) | 176 KB (-21%) |
| UTF-8 압축 | 179 KB (-77%) | 83 KB (-63%) |
| UTF-16LE 압축 | 192 KB (-75%) | 76 KB (-66%) |
| UTF-16BE 압축 | 194 KB (-75%) | 77 KB (-65%) |
보시다시피, UTF-16은 실제 데이터에서 UTF-8보다 약 50% 더 많은 공간을 차지하며, 순수 아시아 텍스트에 대해서만 20%를 절약하고, 범용 압축 알고리즘과는 거의 경쟁이 되지 않습니다. 이 선언문의 중국어 번역은 UTF-16에서 58.8KiB를 차지하지만, UTF-8에서는 51.7KiB만 차지합니다.
인기 있는 텍스트 기반 데이터 형식(예: CSV, XML, HTML, JSON, RTF 및 컴퓨터 프로그램의 소스 코드)은 종종 구조 제어 요소로 ASCII 문자를 포함하며, ASCII와 non-ASCII 텍스트 데이터 문자열을 모두 포함할 수 있습니다. ASCII에서 상속된 코드 포인트가 다른 코드 포인트보다 짧은 가변 길이 인코딩으로 작업하는 것은 문자열 내의 인코딩된 문자 경계를 즉시 알 수 없기 때문에 어려운 작업처럼 보일 수 있습니다. 이로 인해 소프트웨어 설계자들이 UCS-4 고정 폭 인코딩을 선택하게 되었습니다(예: Python v3.3). 실제로 이는 불필요하며 우리가 아는 어떤 실제 문제도 해결하지 않습니다.
이 인코딩의 설계에 의해, UTF-8은 ASCII 문자 값이나 부분 문자열이 절대로 다중 바이트로 인코딩된 문자의 일부와 일치하지 않도록 보장합니다. UTF-16에도 동일하게 적용됩니다. 두 인코딩 모두에서, 다중 부분으로 인코딩된 코드 포인트의 코드 단위는 MSB가 1로 설정되어 있습니다.
예를 들어, HTML 태그의 시작을 표시하는 '<' 기호나 SQL 인젝션을 방어하기 위해 UTF-8로 인코딩된 SQL 문에서 아포스트로피(')를 찾으려면, 모든 영어로 된 일반 텍스트 ASCII 문자열에서 하는 것처럼 하면 됩니다. 인코딩이 이것이 작동하도록 보장합니다. 특히, 모든 비ASCII 문자는 각각 127보다 큰 값을 가진 바이트 시퀀스로 UTF-8에서 인코딩됩니다. 이는 단순하고 빠르며 우아한 순진한 알고리즘에 충돌의 여지를 남기지 않으며, 인코딩된 문자 경계에 대해 신경 쓸 필요가 없습니다.
또한, UTF-8로 인코딩된 비ASCII 부분 문자열을 UTF-8 문자열에서 검색할 때도 마치 일반 바이트 배열인 것처럼 할 수 있습니다—코드 포인트 경계를 고려할 필요가 없습니다. 이것은 UTF-8의 또 다른 설계 기능 덕분입니다—인코딩된 코드 포인트의 선행 바이트는 다른 코드 포인트의 후행 바이트에 해당하는 값을 절대 가질 수 없습니다.
이미 언급했듯이, 유니코드 문자열에서 코드 포인트를 세거나, 분할하거나, 인덱싱하거나, 기타 반복하는 것이 빈번하고 중요한 작업으로 간주되어야 한다는 인기 있는 생각이 있습니다. 이 섹션에서는 이를 더 자세히 검토합니다.
이는 UTF-16이 고정 폭 인코딩이라고 생각하는 사람들의 일반적인 실수입니다. 그렇지 않습니다. 실제로 UTF-16은 가변 길이 인코딩입니다. 비BMP 문자의 존재를 부정하는 경우 이 FAQ를 참조하세요.
이는 잘못 사용된 '문자'라는 단어의 의미에 따라 다릅니다. UTF-32에서 코드 단위와 코드 포인트를 상수 시간에 셀 수 있다는 것은 사실입니다. 그러나 코드 포인트는 사용자가 인식하는 문자에 대응하지 않습니다. 유니코드 형식에서조차 일부 코드 포인트는 _코딩된 문자_에 대응하고 일부는 _비문자_에 대응합니다.
우리는 코드 포인트의 중요성이 자주 과대 평가된다고 생각합니다. 이는 인간 언어의 복잡성을 단순히 반영하는 유니코드의 복잡성에 대한 일반적인 오해 때문입니다. 'Abracadabra'에 문자가 몇 개 있는지 말하기는 쉽지만, 다음 문자열로 돌아가 봅시다:
Приве́т नमस्ते שָׁלוֹם
이것은 22(!)개의 코드 포인트로 구성되어 있지만, 자소 클러스터는 16개뿐입니다. NFC로 변환하면 20개의 코드 포인트로 줄일 수 있습니다. 하지만 이 문자열의 코드 포인트 수는 아마도 UTF-32로 변환하는 것을 제외하고는 거의 모든 소프트웨어 엔지니어링 작업과 무관합니다. 예를 들어:
- 커서 이동, 텍스트 선택 등의 경우 _자소 클러스터_를 사용해야 합니다.
- 입력 필드, 파일 형식, 프로토콜 또는 데이터베이스에서 문자열 길이를 제한하는 경우, 길이는 미리 결정된 인코딩의 _코드 단위_로 측정됩니다. 그 이유는 문자열에 할당된 고정된 메모리 양에서 길이 제한이 파생되기 때문입니다. 이는 메모리, 디스크 또는 특정 데이터 구조에서 모두 마찬가지입니다.
- 화면에 나타나는 문자열의 크기는 문자열의 코드 포인트 수와 무관합니다. 이를 위해서는 렌더링 엔진과 통신해야 합니다. 코드 포인트는 모노스페이스 폰트와 터미널에서조차 한 칸을 차지하지 않습니다. POSIX는 이를 고려합니다.
아니요, 유니코드로 표현할 수 있는 사용자가 인식하는 문자의 수는 사실상 무한하기 때문입니다. 실제로도 대부분의 문자는 완전히 조합된 형태를 가지고 있지 않습니다. 예를 들어, 세 개의 실제 언어로 된 세 개의 실제 단어로 구성된 위의 NFD 문자열은 NFC에서 20개의 코드 포인트로 구성됩니다. 이는 여전히 이 문자열이 가진 16개의 사용자가 인식하는 문자보다 훨씬 많습니다.
라이브러리와 프로그래밍 언어의 유니코드 지원은 종종 '문자열의 길이' 연산이 반환하는 값으로 평가됩니다. 이러한 유니코드 지원 평가에 따르면, C#, Java, 심지어 ICU 자체도 포함한 대부분의 인기 있는 언어들이 유니코드를 지원하지 않는다고 평가될 것입니다. 예를 들어, 하나의 문자로 된 문자열 '🐨'의 길이는 내부 문자열 표현으로 UTF-16을 사용하는 경우 종종 2로 보고되며, UTF-8을 내부적으로 사용하는 언어의 경우 4로 보고됩니다. 이러한 오해의 원인은 이들 언어의 명세서가 '문자'라는 단어를 코드 단위를 의미하는 데 사용하는 반면, 프로그래머는 그것이 다른 무언가일 것이라고 기대하기 때문입니다.
다만, 이러한 API들이 반환하는 코드 단위 수는 실제로 가장 높은 실용적 중요성을 가집니다. UTF-8 문자열을 파일에 쓸 때는 바이트 단위의 길이가 중요합니다. 반면에 다른 종류의 '문자'를 세는 것은 그다지 도움이 되지 않습니다.
UTF-16은 가변 길이이면서도 너무 넓다는 점에서 양쪽 세계의 최악의 조합입니다. 이는 오직 역사적인 이유로만 존재하며 많은 혼란을 야기합니다. 우리는 그 사용이 더욱 감소하기를 희망합니다.
이식성, 크로스 플랫폼 상호운용성 및 단순성은 기존 플랫폼 API와의 상호운용성보다 더 중요합니다. 따라서 가장 좋은 접근 방식은 어디에서나 UTF-8 좁은 문자열을 사용하고, UTF-8을 지원하지 않고 넓은 문자열을 받아들이는 플랫폼 API를 사용할 때(예: Windows API) 이를 앞뒤로 변환하는 것입니다. 문자열을 받아들이는 시스템 API를 다룰 때(예: UI 코드 및 파일 시스템 API) 성능은 거의 문제가 되지 않으며, 애플리케이션의 다른 모든 곳에서 동일한 인코딩을 사용하는 것이 큰 장점이 있으므로, 우리는 다르게 할 충분한 이유를 보지 못합니다.
성능에 대해 말하자면, 기계들은 종종 문자열을 사용하여 통신합니다(예: HTTP 헤더, XML, SOAP). 많은 사람들이 이것 자체를 실수라고 보지만, 그것과 관계없이 이는 거의 항상 영어와 ASCII로 이루어지며, 이는 UTF-8에 추가적인 이점을 줍니다. 다른 종류의 문자열에 대해 다른 인코딩을 사용하는 것은 복잡성을 크게 증가시키고 결과적으로 버그를 발생시킵니다.
특히, 우리는 C++ 표준에 wchar_t를 추가한 것이 실수였다고 믿으며, C++11의 유니코드 추가도 마찬가지입니다. 그러나 구현에서 요구되어야 할 것은 _기본 실행 문자 집합_이 모든 유니코드 데이터를 저장할 수 있어야 한다는 것입니다. 그러면 모든 std::string이나 char* 매개변수가 유니코드와 호환될 것입니다. '이것이 텍스트를 받아들인다면, 유니코드와 호환되어야 한다'—그리고 UTF-8을 사용하면 이를 쉽게 달성할 수 있습니다.
표준 패싯에는 많은 설계상의 결함이 있습니다. 여기에는 std::numpunct, std::moneypunct 및 std::ctype가 가변 길이로 인코딩된 문자(비ASCII UTF-8 및 비BMP UTF-16)를 지원하지 않거나, 변환을 수행하는 데 필요한 정보가 없는 것이 포함됩니다. 이들은 수정되어야 합니다:
decimal_point()와thousands_sep()은 단일 코드 단위 대신 문자열을 반환해야 합니다. C 로케일이localeconv함수를 통해 이렇게 하는 것처럼요(비록 사용자 정의는 불가능하지만).toupper()와tolower()는 유니코드에서 작동하지 않기 때문에 코드 단위 측면에서 표현되어서는 안 됩니다. 예를 들어, 라틴어 ffl 합자는 FFL로 변환되어야 하고 독일어 ß는 SS로 변환되어야 합니다(ẞ라는 대문자 형태가 있지만, 대소문자 변환 규칙은 전통적인 것을 따릅니다). 또한, 일부 언어(예: 그리스어)는 일부 소문자의 특별한 최종 형태를 가지고 있어서, 대소문자 변환 루틴이 변환을 올바르게 수행하기 위해서는 그들의 위치를 알아야 합니다.
이 섹션은 다중 플랫폼 라이브러리 개발과 Windows 프로그래밍에 관한 것입니다. Windows 플랫폼의 문제는 유니코드와 호환되는 좁은 문자열 시스템 API를 (아직) 지원하지 않는다는 것입니다. Windows API에 유니코드 문자열을 전달하는 유일한 방법은 UTF-16(넓은 문자열이라고도 함)으로 변환하는 것입니다.
우리의 지침은 Microsoft의 원래 유니코드 변환 가이드와 상당히 다르다는 점에 유의하세요. 우리의 접근 방식은 넓은 문자열 변환을 API 호출에 가능한 한 가깝게 수행하고, 절대로 넓은 문자열 데이터를 보유하지 않는 것에 기반합니다. 이전 섹션에서 우리는 이것이 일반적으로 더 나은 성능, 안정성, 코드 단순성 및 다른 소프트웨어와의 상호운용성을 가져올 것이라고 설명했습니다.
-
UTF-16을 받아들이는 API에 인접한 곳을 제외하고는 어떤 곳에서도
wchar_t나std::wstring을 사용하지 마세요. -
UTF-16을 받아들이는 API에 대한 매개변수를 제외하고는 어떤 곳에서도
_T("")나L""리터럴을 사용하지 마세요. -
LPTSTR,CreateWindow()또는_T()매크로와 같이UNICODE상수에 민감한 타입, 함수 또는 그들의 파생물을 사용하지 마세요. 대신LPWSTR,CreateWindowW()및 명시적L""리터럴을 사용하세요. -
그러나
UNICODE와_UNICODE는 항상 정의되어 있어야 합니다. 이는 좁은 UTF-8 문자열이 ANSI WinAPI로 전달되는 것이 암묵적으로 컴파일되는 것을 방지하기 위해서입니다. 이는 VS 프로젝트 설정에서 유니코드 문자 집합 사용 코드명으로 설정할 수 있습니다. -
std::string과char*변수는 프로그램의 어디에서나 UTF-8로 간주됩니다. -
C++로 작성하는 특권이 있다면, 아래의
narrow()/widen()변환 함수를 인라인 변환 구문에 사용하면 편리합니다. 물론, 다른 UTF-8/UTF-16 변환 코드도 사용할 수 있습니다. -
와이드차(
LPWSTR)를 받아들이는 Win32 함수만 사용하고,LPTSTR이나LPSTR을 받아들이는 함수는 절대 사용하지 마세요. 다음과 같이 매개변수를 전달하세요:
::SetWindowTextW(widen(someStdString or "string litteral").c_str())이 정책은 아래에서 설명하는 변환 함수를 사용합니다. 변환 성능에 대한 참고사항도 참조하세요.
- MFC 문자열의 경우:
CString someoneElse; // MFC에서 온 것
// API 호출에서 더 멀리 전달하기 전에 가능한 한 빨리 변환:
std::string s = str(boost::format("Hello %s\n") % narrow(someoneElse));
AfxMessageBox(widen(s).c_str(), L"Error", MB_OK);- .NET 개발자를 위해: 기본 UTF-16 기반 문자열 클래스 사용을 피하기는 어려울 수 있습니다. 이 구현 세부사항이 이 클래스의 인터페이스를 통해 크게 누출된다는 점을 기억하세요. 예를 들어,
string[index]연산은 문자의 일부를 반환할 수 있습니다(UTF-8 바이트 배열에서와 마찬가지로). 문자열을 출력 파일이나 통신 장치로 직렬화할 때는Encoding.UTF8을 지정하는 것을 잊지 마세요. ASP.NET 웹 애플리케이션과 같이 일반적으로 UTF-8 HTML 출력을 생성하는 경우 변환에 따른 성능 페널티를 감수할 준비를 하세요.
- 항상 텍스트 출력 파일을 UTF-8로 생성하세요.
- RAII/OOD 이유로
fopen()은 어차피 피해야 합니다. 하지만 필요한 경우_wfopen()과 위에서 설명한 WinAPI 규칙을 사용하세요. fstream패밀리에std::string이나const char*파일명 인자를 절대 전달하지 마세요. MSVC CRT는 UTF-8 인자를 지원하지 않지만, 다음과 같이 사용해야 하는 비표준 확장을 가지고 있습니다:widen으로std::string인자를std::wstring으로 변환하세요:
std::ifstream ifs(widen("hello"), std::ios_base::binary);MSVC의 fstream에 대한 태도가 바뀌면 이 변환을 수동으로 제거해야 할 것입니다.
- 이 코드는 다중 플랫폼이 아니며 향후에 수동으로 변경해야 할 수 있습니다.
- 또는 변환을 숨기는 래퍼 세트를 사용하세요.
이 지침은 Boost.Nowide 라이브러리(아직 boost의 일부가 아님)의 변환 함수를 사용합니다:
std::string narrow(const wchar_t *s);
std::wstring widen(const char *s);
std::string narrow(const std::wstring &s);
std::wstring widen(const std::string &s);이 라이브러리는 또한 파일을 다루는 표준 C와 C++ 라이브러리 함수들에 대한 래퍼 세트와 iostreams를 통해 UTF-8을 읽고 쓰는 수단을 제공합니다.
이러한 함수들과 래퍼들은 Windows의 MultiByteToWideChar와 WideCharToMultiByte 함수를 사용하여 쉽게 구현할 수 있습니다. 다른 (가능하면 더 빠른) 변환 루틴을 사용할 수도 있습니다.
A: 아니요, 저는 Windows에서 성장했고, 주로 Windows 개발자입니다. 저는 Microsoft가 텍스트 영역에서 잘못된 설계 선택을 했다고 믿습니다. 왜냐하면 그들이 다른 사람들보다 더 일찍 그렇게 했기 때문입니다.
A: 아니요, 그리고 제 나라는 비ASCII 언어를 사용합니다. ASCII 문자를 단일 바이트로 인코딩하는 형식을 사용하는 것이 영미 중심주의라거나 인간 상호작용과 관련이 있다고 생각하지 않습니다. 프로그램의 소스 코드, 웹 페이지, XML 파일, OS 파일 이름 및 기타 컴퓨터 간 텍스트 인터페이스가 절대 존재해서는 안 된다고 주장할 수 있지만, 그것들이 존재하는 한, 텍스트가 항상 인간 독자를 위한 것은 아닙니다.
A: 이것은 거짓입니다. C#과 Java 모두 16비트 char 타입을 제공하는데, 이는 유니코드 문자보다 작습니다. 축하합니다. .NET 인덱서 str[i]는 내부 표현의 단위로 작동하므로, 다시 한번 추상화가 누출됩니다. 하위 문자열 메서드는 비BMP 문자를 부분으로 잘라서 잘못된 문자열을 반환할 것입니다.
더욱이, 텍스트를 디스크의 파일, 네트워크 통신, 외부 장치 또는 다른 프로그램이 읽을 수 있는 모든 곳에 쓸 때는 인코딩에 신경 써야 합니다. 이러한 경우에는 가정된 내용에 관계없이 System.Text.Encoding.UTF8 (.NET)을 사용하시기 바랍니다. 절대로 Encoding.ASCII, UTF-16 또는 휴대폰 PDU를 사용하지 마세요.
ASP.NET과 같은 웹 프레임워크는 기본 프레임워크의 내부 문자열 표현의 잘못된 선택으로 인해 고통받습니다: 웹 애플리케이션의 예상 문자열 출력(및 입력)은 거의 항상 UTF-8이며, 이로 인해 고처리량 웹 애플리케이션과 웹 서비스에서 상당한 변환 오버헤드가 발생합니다.
A: UTF-8이 원래 호환성 해킹으로 만들어졌는지와 관계없이, 오늘날 그것은 유니코드의 다른 어떤 것보다 더 나은, 더 인기 있는 인코딩입니다.
Q: 두 바이트 이상을 차지하는 UTF-16 문자는 실제 세계에서 매우 드뭅니다. 이는 실질적으로 UTF-16을 고정 폭 인코딩으로 만들어, 많은 장점을 제공합니다. 이러한 문자들을 무시할 수 없나요?
A: 소프트웨어 설계에서 유니코드 전체를 지원하지 않겠다는 것을 진지하게 말씀하시는 건가요? 그리고 어쨌든 결국 지원하게 될 텐데, 비BMP 문자가 드물다는 사실이 소프트웨어 테스트를 더 어렵게 만드는 것 외에 실제로 무엇을 바꿀 수 있나요? 실제 애플리케이션에서 문자열을 그대로 전달하는 것에 비해 텍스트 조작이 상대적으로 드물다는 것이 중요합니다. 이는 "거의 고정 폭"이 거의 성능 이점을 가지지 않는다는 것을 의미하는 반면(성능 참조), 더 짧은 문자열을 가지는 것이 중요할 수 있습니다.
A: 우리는 어떤 인코딩을 올바르게 사용하는 것에 대해 반대하지 않습니다. 그러나 같은 타입, 예를 들어 std::string이 다른 맥락에서 다른 의미를 가질 때 문제가 됩니다. 일부에게는 'ANSI 코드페이지'이고, 다른 이들에게는 '이 코드는 깨졌고 비영어 텍스트를 지원하지 않는다'는 것을 의미합니다. 우리의 프로그램에서는 유니코드를 인식하는 UTF-8 문자열을 의미합니다. 이러한 다양성은 많은 버그와 많은 고통의 원천입니다. 이러한 추가적인 복잡성은 세상이 정말로 필요로 하지 않는 것입니다. 그 결과는 업계 전반에 걸쳐 유니코드가 깨진 많은 소프트웨어입니다. JoelOnSoftware는 모든 프로그래머가 인코딩을 인식하는 것이 유니코드가 깨진 소프트웨어의 해결책이라고 제안합니다. 우리는 하나의 주류 인코딩이 소프트웨어 API의 기본값이 되면, 텍스트와 언어 문제의 전문가가 되지 않고도 올바른 파일 복사 프로그램을 작성할 수 있을 것이라고 믿습니다.
Q: 내 애플리케이션은 GUI만 있습니다. IP 통신이나 파일 IO를 하지 않습니다. 왜 Windows API 호출을 위해 문자열을 계속 앞뒤로 변환해야 하나요? 단순히 넓은 상태 변수를 사용하는 대신에요?
A: 이것은 유효한 지름길입니다. 실제로 넓은 문자열을 사용하는 정당한 경우일 수 있습니다. 하지만 나중에 구성이나 로그 파일을 추가할 계획이라면, 전체를 좁은 문자열로 변환하는 것을 고려해 보세요. 그것이 미래에 대비하는 것일 것입니다.
A: 이는 UTF-8 char* 문자열이 ANSI를 기대하는 Windows API 함수 변형에 꽂히는 것을 막는 추가적인 안전 메커니즘입니다. 우리는 이것이 컴파일러 오류를 생성하기를 원합니다. Windows에서 argv[] 문자열을 fopen()에 전달하는 것과 같은 종류의 찾기 어려운 버그입니다: 사용자가 절대로 현재 코드페이지가 아닌 파일 이름을 전달하지 않을 것이라고 가정합니다. 테스터들이 가끔 중국어 파일 이름을 제공하도록 훈련받지 않는 한 수동 테스트로는 이런 종류의 버그를 찾기 어려울 것이며, 그래도 여전히 깨진 프로그램 로직입니다. UNICODE 정의 덕분에, 그런 경우에 컴파일러 오류를 얻게 됩니다.
A: 먼저 그들이 유효한 코드페이지로 CP_UTF8을 지원하기 시작하는 것을 봅시다. 이것은 그리 어려운 일이 아닐 것입니다. 그렇게 되면, 우리는 어떤 Windows 개발자라도 와이드차 API를 계속 사용할 이유가 없을 것이라고 봅니다. 또한, CP_UTF8 지원을 추가하면 기존의 유니코드가 깨진 일부 프로그램과 라이브러리가 '고쳐질' 것입니다.
일부는 CP_UTF8 지원 추가가 ANSI API를 사용하는 기존 애플리케이션을 '깨뜨릴' 것이며, 이것이 Microsoft가 넓은 문자열 API를 만들어야 했던 이유라고 주장합니다. 이는 사실이 아닙니다. 심지어 일부 인기 있는 ANSI 인코딩도 가변 길이입니다(예: Shift JIS), 따라서 올바른 코드는 깨지지 않을 것입니다. Microsoft가 UCS-2를 선택한 이유는 순전히 역사적입니다. 당시에는 UTF-8이 아직 존재하지 않았고, 유니코드는 '그저 더 넓은 ASCII'로 여겨졌으며, 고정 폭 인코딩을 사용하는 것이 중요하다고 생각되었습니다.
A: 유니코드 표준(v6.2, p.30)에 따르면: UTF-8의 경우 BOM의 사용은 필요하지도 권장되지도 않습니다.
바이트 순서 문제는 UTF-16을 피해야 하는 또 다른 이유입니다. UTF-8은 엔디안 문제가 없으며, UTF-8 BOM은 이것이 UTF-8 스트림이라는 것을 나타내기 위해서만 존재합니다. UTF-8이 (이미 인터넷 세계에서 그런 것처럼) 유일하게 인기 있는 인코딩으로 남는다면, BOM은 중복됩니다. 실제로 오늘날 대부분의 UTF-8 텍스트 파일은 BOM을 생략합니다.
BOM을 사용하려면 파일 연결과 같은 단순한 시나리오에서도 모든 기존 코드가 이를 인식해야 합니다. 이는 받아들일 수 없습니다.
A: Windows에서도 항상 \n (0x0a) 줄 끝을 사용하세요. 파일은 바이너리 모드로 읽고 써야 하며, 이는 상호운용성을 보장합니다—프로그램이 항상 어떤 시스템에서든 같은 출력을 제공할 것입니다. C와 C++ 표준이 \n을 메모리 내 줄 끝으로 사용하므로, 이는 모든 파일이 POSIX 규칙으로 쓰여지게 할 것입니다. Windows의 노트패드에서 파일을 열 때 문제가 될 수 있습니다. 하지만 어떤 괜찮은 텍스트 편집기도 그러한 줄 끝을 이해합니다.
우리는 또한 SI 단위, ISO-8601 날짜 형식, 그리고 부동 쉼표 대신 부동 소수점을 선호합니다.
A: UTF-16에서 정말 더 나은가요? 아마도 그럴 수 있습니다. ICU는 역사적인 이유로 UTF-16을 사용하므로, 측정하기가 꽤 어렵습니다. 하지만 대부분의 경우 문자열은 쿠키처럼 취급되며, 매 두 번째 사용마다 정렬되거나 뒤집히지 않습니다. 더 밀집된 인코딩이 그런 경우 성능에 유리합니다.
A: 꼭 그렇지는 않습니다. 하지만 그렇습니다. 안전성은 모든 설계의 중요한 기능이며, 인코딩도 예외가 아닙니다.
A: 일반 텍스트라는 것은 없습니다. 코드페이지-ANSI나 ASCII 전용 텍스트를 '문자열'이라는 이름의 클래스에 저장할 이유가 없습니다.
A: 첫째, 어떤 방식으로든 일부 변환을 하게 될 것입니다. 시스템을 호출할 때나, 또는 TCP를 통해 텍스트 문자열을 보내는 것과 같이 나머지 세계와 상호작용할 때 그렇습니다. 또한, 문자열을 받아들이는 OS API는 종종 UI나 파일 시스템 작업과 같이 본질적으로 느린 작업을 수행합니다. 시스템 API와의 상호작용이 애플리케이션을 지배한다면, 여기 작은 실험이 있습니다.
OS API의 일반적인 사용은 파일을 여는 것입니다. 이 함수는 내 기계에서 (184 ± 3)μs가 걸립니다:
void f(const wchar_t* name)
{
HANDLE f = CreateFile(name, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
DWORD written;
WriteFile(f, "Hello world!\n", 13, &written, 0);
CloseHandle(f);
}반면에 이것은 (186 ± 0.7)μs가 걸립니다:
void f(const char* name)
{
HANDLE f = CreateFile(widen(name).c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
DWORD written;
WriteFile(f, "Hello world!\n", 13, &written, 0);
CloseHandle(f);
}(두 경우 모두 name="D:\\a\\test\\subdir\\subsubdir\\this is the sub dir\\a.txt"로 실행됨. 5회 실행의 평균입니다. C++11에서 제공하는 std::string 연속 저장 보장에 의존하는 최적화된 widen을 사용했습니다.)
이는 단지 (1 ± 2)%의 오버헤드입니다. 또한, MultiByteToWideChar는 가장 빠른 UTF-8↔UTF-16 변환 함수가 아닙니다.
A: 소프트웨어를 국제화한다면 모든 비ASCII 문자열은 외부 번역 데이터베이스에서 로드될 것이므로 문제가 되지 않습니다.
그래도 특수 문자를 포함하고 싶다면 다음과 같이 할 수 있습니다. C++11에서는 다음과 같이 할 수 있습니다:
u8"∃y ∀x ¬(x ≺ y)"'u8'을 지원하지 않는 컴파일러에서는 다음과 같이 UTF-8 코드 단위를 하드코딩할 수 있습니다:
"\xE2\x88\x83y \xE2\x88\x80x \xC2\xAC(x \xE2\x89\xBA y)"하지만 가장 직접적인 방법은 문자열을 그대로 작성하고 소스 파일을 UTF-8로 저장하는 것입니다:
"∃y ∀x ¬(x ≺ y)"불행히도, MSVC는 이것을 어떤 ANSI 코드페이지로 변환하여 문자열을 손상시킵니다. 이를 해결하려면 파일을 BOM 없이 UTF-8로 저장하세요. MSVC는 이것이 올바른 코드페이지에 있다고 가정하고 문자열을 건드리지 않을 것입니다. 하지만, 이로 인해 유니코드 식별자와 넓은 문자열 리터럴(어차피 사용하지 않을)을 사용할 수 없게 됩니다.
A: char를 유지하세요. UNICODE와 _UNICODE를 정의하여 narrow()/widen()이 사용되어야 하는 곳에서 컴파일러 오류를 받으세요(이는 Visual Studio 프로젝트 설정에서 유니코드 문자 집합 사용을 설정하면 자동으로 수행됩니다). 모든 fstream과 fopen() 사용을 찾아서 위에서 설명한 대로 넓은 오버로드를 사용하세요. 이제 거의 다 되었습니다.
유니코드를 지원하지 않는 3rd-파티 라이브러리를 사용한다면, 예를 들어 파일 이름 문자열을 그대로 fopen()에 전달하는 경우, 위에서 보여준 대로 GetShortPathName()과 같은 도구를 사용하여 해결해야 할 것입니다.
A: 아마도, 그들이 덜 했더라면 지원이 더 나았을 것입니다. CPython v3.3 참조 구현에서는 내부 문자열 표현이 변경되었습니다. UTF-16은 실제 문자열 내용에 따라 세 가지 가능한 인코딩(ISO-8859-1, UCS-2 또는 UCS-4) 중 하나로 대체되었습니다. 단일 비ASCII 또는 비BMP 문자를 추가하면, 전체 문자열이 종종 암시적으로 다른 인코딩으로 변환될 것입니다. 내부 인코딩은 스크립트에 투명합니다. 이 설계는 유니코드 코드 포인트에 대한 인덱싱 연산의 성능을 최적화하기 위한 것입니다. 하지만 우리는 대부분의 사용에서 코드 포인트를 세거나 인덱싱하는 것이 중요하지 않아야 한다고 주장합니다—예를 들어, 자소 클러스터와 비교하면 말입니다. 우리가 알기로는 Python은 현재 후자에 대한 지원을 전혀 제공하지 않습니다.
따라서, 우리는 문자열의 표현 불가지론적 처리보다는 UTF-8 내부 표현과 함께 표현-투명한 API를 선호합니다. 인덱싱 연산은 코드 포인트가 아닌 코드 단위를 세게 될 것이며, 실제로 변경 전에 그랬던 것처럼 말입니다. 이는 또한 구현을 단순화하고 성능도 향상시킬 것입니다. 예를 들어, 이미 UTF-8 인코딩된 텍스트가 지배적인 웹을 다루는 스크립트에서 말입니다. 이로써 Python 프로그래밍 언어가 서버 사이드 세계에 더 적용 가능하게 만들 것입니다. 스크립트 프로그래머들의 문자열 자르기 연산의 안전성에 대해 논쟁할 수 있지만, 그러면 다시 자소 클러스터를 분할하는 것에 대해서도 같은 주장이 유효합니다. 유니코드가 이제 완전히 지원되지만, 우리는 Python이 역사적 부담이 적은 현대적인 도구로서 텍스트 처리에서 더 나은 작업을 해야 한다고 믿습니다.
그 외에도, JPython과 IronPython은 계속해서 그들의 호스팅 플랫폼(각각 Java와 .NET)에서 사용하는 덜 운 좋은 인코딩에 의존하며, 거기서는 서로게이트 쌍을 올바르게 처리하도록 주의를 기울여야 합니다.
A: 문자열을 다루는 모든 코드가 실제로 텍스트의 처리와 유효성 검사에 관여하는 것은 아닙니다. 유니코드 파일 이름을 받아서 파일 IO 루틴에 전달하는 파일 복사 프로그램은 간단한 바이트 버퍼만으로도 충분할 것입니다. 문자열을 받아들이는 라이브러리를 설계한다면, 단순하고 표준적이며 가벼운 std::string이면 충분할 것입니다. 반대로, 새로운 문자열 클래스를 재발명하고 모든 사람에게 당신만의 특별한 인터페이스를 강요하는 것은 실수일 것입니다. 물론, 단순히 문자열을 주고받는 것 이상이 필요하다면, 그때는 적절한 텍스트 처리 도구를 사용해야 합니다. 하지만, 그러한 도구는 STL의 컨테이너/알고리즘 분리 정신에 따라 사용된 저장 클래스와 독립적인 것이 더 좋습니다. 실제로, 일부는 std::string 인터페이스가 너무 비대하다고 생각하며, 그 대부분은 std::string 클래스 밖으로 이동되었어야 했다고 봅니다.
A: 이 말을 전파하세요.
코드를 검토하고 어떤 라이브러리가 이식 가능한 유니코드 인식 코드에서 사용하기 가장 고통스러운지 보세요. 작성자들에게 버그 리포트를 여세요. C나 C++ 라이브러리 작성자라면, UTF-8이 함축된 char*와 std::string을 사용하고, ANSI 코드 페이지 지원을 거부하세요—그것들은 본질적으로 유니코드가 깨져있기 때문입니다.
Microsoft 직원이라면, CP_UTF8을 좁은 API 코드 페이지 중 하나로 구현하도록 밀어붙이세요.
추가 아이디어:
- 일반적으로 사용되는 3rd-파티 라이브러리(예: PugiXML, LibTIFF 등)의 UTF-8 지원을 위한 패치 저장소를 만드세요. 이들은 표준 C로 작성되었으며 Windows에 대해 신경 쓰지 않습니다.
- Windows에서 표준 라이브러리 함수(예:
fopen())에 대한 링크 타임 패치를 만들어서 적절한 매개변수 변환을 수행하도록 하세요. 이는main()과 전역 환경 변수에 대해 수행될 수 있습니다.
이 선언문은 Pavel Radzivilovsky, Yakov Galka, Slava Novgorodov가 작성했습니다. 이는 실제 세계의 유니코드 이슈와 실제 세계의 프로그래머들이 저지른 실수에 대한 우리의 경험과 연구의 결과입니다. 여기서 우리의 목표는 텍스트 이슈에 대한 인식을 높이고 유니코드 인식 프로그래밍을 더 쉽게 만들기 위한 업계 전반의 변화를 촉발하여, 궁극적으로 인간 엔지니어들이 작성한 그러한 프로그램의 사용자 경험을 개선하는 것입니다. 우리 중 누구도 유니코드 컨소시엄에 관여하지 않았습니다.
Python에 대한 정보를 제공해 준 Glenn Linderman, 그리고 이 문서의 버그와 오타를 보고해 준 Markus Künne, Jelle Geerts, Lazy Rui, Jan Rüegg에게 특별한 감사를 드립니다.
텍스트의 많은 부분은 Boost.Locale의 저자인 Artyom Beilis가 시작한 StackOverflow의 토론에서 영감을 받았습니다. 추가적인 영감은 VisionMap의 개발 규칙과 Michael Hartl의 tauday.org에서 왔습니다.
- The Unicode Consortium (The Unicode Standard, PDF)
- International Components for Unicode (ICU)
- Boost.Locale—C++ 방식의 고품질 지역화 기능.
- Artyom Beilis가 시작한 StackOverflow의 Should UTF-16 be considered harmful
- How twitter counts characters