C プログラミング:Unicode のプログラミング方法は?

C99 以前

C 標準 (C99) はワイド文字とマルチバイト文字を提供しますが、これらのワイド文字が何を保持できるかについて保証がないため、それらの値は多少制限されます。特定の実装に対して、それらは有用なサポートを提供しますが、コードが実装間を移動できる必要がある場合、それらが有用であるという保証は不十分です.

したがって、Hans van Eck によって提案されたアプローチ (ICU - International Components for Unicode - ライブラリのラッパーを作成すること) は健全です。IMO.

UTF-8 エンコーディングには多くの利点があります。その 1 つは、データをいじらない場合 (たとえば、データを切り捨てるなど)、UTF-8 の複雑さを完全には認識していない関数によってコピーできることです。エンコーディング。これは、wchar_t には当てはまりません。 .

完全な Unicode は 21 ビット形式です。つまり、Unicode は U+0000 から U+10FFFF までのコード ポイントを予約しています。

UTF-8、UTF-16、および UTF-32 形式 (UTF は Unicode Transformation Format の略です。Unicode を参照) の便利な点の 1 つは、情報を失うことなく 3 つの表現間で変換できることです。それぞれは、他の人が表すことができるものなら何でも表すことができます。 UTF-8 と UTF-16 はどちらもマルチバイト形式です。

UTF-8 はマルチバイト形式であることがよく知られており、文字列内の任意の位置から始まる、文字列内の文字の開始を確実に見つけることを可能にする慎重な構造を備えています。 1 バイト文字の上位ビットはゼロに設定されています。マルチバイト文字は、ビット パターン 110、1110、または 11110 (2 バイト、3 バイト、または 4 バイト文字の場合) のいずれかで始まる最初の文字を持ち、後続のバイトは常に 10 で始まります。継続文字は常に範囲 0x80 .. 0xBF。 UTF-8 文字は可能な限り最小限の形式で表現する必要があるという規則があります。これらのルールの結果の 1 つは、バイト 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

当初、Unicode は 16 ビットのコード セットであり、すべてが 16 ビットのコード空間に収まることが期待されていました。残念ながら、現実の世界はもっと複雑で、現在の 21 ビット エンコーディングに拡張する必要がありました.

したがって、UTF-16 は「基本多言語面」の単一単位 (16 ビット ワード) コード セットであり、Unicode コード ポイント U+0000 .. U+FFFF を持つ文字を意味しますが、2 つの単位 (32 ビット) を使用します。この範囲外の文字。したがって、UTF-16 エンコーディングで動作するコードは、UTF-8 が必要とするのと同様に、可変幅エンコーディングを処理できなければなりません。ダブルユニット文字のコードはサロゲートと呼ばれます。

もちろん、UTF-32 は、任意の Unicode コード ポイントを 1 つのストレージ ユニットにエンコードできます。計算には効率的ですが、保存には効率的ではありません。

ICU および Unicode Web サイトでさらに多くの情報を見つけることができます。

C11 と <uchar.h>

C11 標準ではルールが変更されましたが、現在 (2017 年半ば) でもすべての実装が変更に追いついているわけではありません。 C11 標準は、Unicode サポートの変更点を次のようにまとめています。

以下は、機能の最小限の概要です。仕様には以下が含まれます:

(相互参照の翻訳:<stddef.h> size_t を定義 ,<wchar.h> mbstate_t を定義 、および <stdint.h> uint_least16_t を定義 および uint_least32_t .)<uchar.h> ヘッダーは、(再起動可能な) 変換関数の最小限のセットも定義します:

\unnnn を使用して識別子に使用できる Unicode 文字についての規則があります。 または \U00nnnnnn 表記。識別子でそのような文字のサポートを積極的に有効にする必要がある場合があります。たとえば、GCC には -fextended-identifiers が必要です 識別子でこれらを許可します。

macOS Sierra (10.12.5) は、プラットフォームの 1 つに過ぎませんが、<uchar.h> をサポートしていないことに注意してください。 .


これは「厳密な Unicode プログラミング」自体に関するものではなく、実際の経験に関するものであることに注意してください。

私の会社で行ったことは、IBM の ICU ライブラリのラッパー ライブラリを作成することでした。ラッパー ライブラリには UTF-8 インターフェイスがあり、ICU を呼び出す必要がある場合は UTF-16 に変換されます。私たちの場合、パフォーマンスへの影響についてはあまり心配していませんでした。パフォーマンスが問題になる場合は、UTF-16 インターフェイスも提供しました (独自のデータ型を使用)。

アプリケーションはほとんどそのまま (char を使用) のままにすることができますが、場合によっては、特定の問題に注意する必要があります。たとえば、strncpy() の代わりに、UTF-8 シーケンスの切断を回避するラッパーを使用します。私たちの場合、これで十分ですが、結合文字のチェックを検討することもできます。コードポイントの数、書記素の数などをカウントするためのラッパーもあります。

他のシステムと接続する場合、カスタム キャラクター構成を行う必要がある場合があるため、(アプリケーションによっては) ある程度の柔軟性が必要になる場合があります。

wchar_t は使用しません。 ICU を使用すると、移植性に関する予期しない問題が回避されます (もちろん、他の予期しない問題は回避されません:-)。


この FAQ は豊富な情報です。そのページと Joel Spolsky によるこの記事の間で、良いスタートが切れるでしょう。

途中でたどり着いた 1 つの結論:

    <リ>

    wchar_t Windows では 16 ビットですが、他のプラットフォームでは必ずしも 16 ビットではありません。 Windows では必要悪だと思いますが、他の場所ではおそらく回避できるでしょう。 Windows で重要な理由は、名前に非 ASCII 文字を含むファイルを使用する必要があるためです (関数の W バージョンと共に)。

    <リ>

    wchar_t を取る Windows API に注意してください。 文字列は UTF-16 エンコーディングを想定しています。これは UCS-2 とは異なることにも注意してください。サロゲート ペアに注意してください。このテスト ページには、わかりやすいテストがあります。

    <リ>

    Windows でプログラミングしている場合、fopen() は使用できません。 、 fread()fwrite() など char * しかとらないので UTF-8エンコーディングを理解していません。移植性が苦痛になります。