Cプログラミング:Unicode用にプログラミングする方法は?


82

厳密なUnicodeプログラミングを行うにはどのような前提条件が必要ですか?

これは、私のコードがcharどこでも型を使用してはならず、wint_tおよびを処理できる関数を使用する必要があることを意味しwchar_tますか?

そして、このシナリオでマルチバイト文字シーケンスが果たす役割は何ですか?

回答:


21

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

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

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

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

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


2
有効なUTF-8バイトシーケンスがstrncpyによって切断(切り捨て)されることはありません。有効なUTF-8シーケンスには、0x00バイトを含めることはできません(もちろん、終了するnullバイトを除く)。
ダンモールディング

8
@Dan Moulding:たとえば、1つの漢字(3バイトの場合もある)を含む文字列を2バイトのchar配列にstrncpy()すると、無効なUTF-8シーケンスが作成されます。
Hans van Eck 2010

@Hans van Eck:ラッパーがその単一の3バイト漢字を2バイト配列にコピーする場合、それを切り捨てて無効なシーケンスを作成するか、未定義の動作が発生します。明らかに、データをコピーする場合、ターゲットは十分に大きい必要があります。それは言うまでもない。私のポイントは、strncpy適切に使用すると、UTF-8で完全に安全に使用できるということでした。
ダンモールディング

5
@DanMoulding:ターゲットバッファが十分に大きいことがわかっている場合は、そのまま使用できますstrcpy(UTF-8で使用しても安全です)。使用strncpyしている人、ターゲットバッファが十分に大きいかどうかわからないため、おそらくそうします。そのため、コピーする最大バイト数を渡したいので、実際に無効なUTF-8シーケンスが作成される可能性があります。
Frerich Raabe 2013

41

C99以前

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

したがって、Hans van Eckによって提案されたアプローチ(ICUのラッパーを作成すること-Unicodeの国際コンポーネント-ライブラリ)は健全なIMOです。

UTF-8エンコーディングには多くのメリットがあります。その1つは、データを(たとえば、データを切り捨てて)混乱させない場合、UTF-8の複雑さを十分に認識していない関数によってコピーできることです。エンコーディング。これは、wchar_t。の場合とはまったく異なります。

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

(UTFはUnicodeの変換形式の略-を参照してUTF-8、UTF-16とUTF-32フォーマットについての有用なことの一つUnicodeは)あなたが情報の損失なしに3つの表現の間で変換することができますということです。それぞれが他の人が表現できるものなら何でも表現できます。UTF-8とUTF-16はどちらもマルチバイト形式です。

UTF-8はマルチバイト形式であることがよく知られており、文字列内の任意のポイントから開始して、文字列内の文字の先頭を確実に見つけることができるように注意深い構造を備えています。シングルバイト文字の上位ビットはゼロに設定されています。マルチバイト文字の最初の文字は、ビットパターン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と同様に、可変幅エンコーディングを処理できる必要があります。ダブルユニット文字のコードはサロゲートと呼ばれます。

サロゲートは、Unicode値の2つの特別な範囲からのコードポイントであり、UTF-16のペアのコードユニットの先頭と末尾の値として使用するために予約されています。先行(高)サロゲートはU + D800からU + DBFFとも呼ばれ、後続または低サロゲートはU + DC00からU + DFFFです。それらは文字を直接表すのではなく、ペアとしてのみ表すため、サロゲートと呼ばれます。

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

あなたはICUでより多くの情報を見つけることができますおよびUnicodeのWebサイトます。

C11と <uchar.h>

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

  • Unicode文字と文字列(<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進数16進数

7.28Unicodeユーティリティ <uchar.h>

ヘッダーは<uchar.h>、Unicode文字を操作するためのタイプと関数を宣言します。

宣言mbstate_tされたタイプは(7.29.1で説明)およびsize_t(7.19で説明)です。

char16_t

これは、16ビット文字に使用される符号なし整数型であり、uint_least16_t(7.20.1.2で説明されている)と同じ型です。そして

char32_t

これは、32ビット文字に使用される符号なし整数型であり、uint_least32_t(7.20.1.2でも説明されている)と同じ型です。

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

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

\unnnnor\U00nnnnnn表記を使用する識別子で使用できるUnicode文字に関する規則があります。識別子内のそのような文字のサポートを積極的にアクティブ化する必要がある場合があります。たとえば、GCCには-fextended-identifiersこれらを識別子で許可するます。

macOS Sierra(10.12.5)は、1つのプラットフォームを挙げれば、をサポートしていないことに注意してください<uchar.h>


3
私はあなたがwchar_tここで少し短い友人を売っていると思います。これらのタイプは、Cライブラリが任意のエンコーディング(非Unicodeエンコーディングを含む)でテキストを処理できるようにするために不可欠です。ワイド文字タイプと関数がないと、Cライブラリには、サポートされているすべてのエンコーディングに対して一連のテキスト処理関数が必要になります。KOI-8でエンコードされたテキスト専用のkoi8len、koi8tok、koi8printf、およびUTF-8用のutf8len、utf8tok、utf8printfを想像してください。テキスト。代わりに、私たちは持っているラッキーです1これらの機能のセット(オリジナルASCIIのものをカウントしない): 、wcslenwcstokwprintf
ダンモールディング

1
プログラマーが行う必要があるのは、Cライブラリーの文字変換関数(mbstowcsおよびその仲間)を使用して、サポートされているエンコードをに変換することだけwchar_tです。wchar_tフォーマットが完了すると、プログラマーはCライブラリが提供するワイドテキスト処理関数の単一セットを使用できます。優れたCライブラリの実装は、ほとんどのプログラマーが必要とするほぼすべてのエンコーディングをサポートします(私のシステムの1つでは、221の固有のエンコーディングにアクセスできます)。
ダンモールディング

それらが有用であるのに十分な幅であるかどうかに関して:標準は、実装wchar_tによってサポートされる任意の文字を含むのに十分な幅の実装を保証する必要があります。これは、(おそらく1つの注目すべき例外を除いて)ほとんどの実装が、を使用wchar_tするプログラムがシステムでサポートされるすべてのエンコーディングを処理できるように十分な幅を確保することwchar_tを意味します(Microsoftの幅は16ビットしかないため、実装がすべてのエンコーディングを完全にサポートするわけではありません。最も顕著なのはさまざまなUTFエンコーディングですが、それらは例外であり、ルールではありません)。
ダンモールディング

11

このFAQは豊富な情報です。そのページとJoelSpolskyによるこの記事の間で、良いスタートを切ることができます。

私が途中で得た1つの結論:

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

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

  • あなたは、Windowsにしている番組は、使用できない場合はfopen()fread()fwrite()、など彼らだけ取るため、char *およびUTF-8エンコーディングを理解していません。持ち運びに苦労します。


stdioのことを注意f*して友人と仕事char *上のすべての標準がそう言うのでプラットフォーム-使用wcs*wchar_tのための代わりに。

7

厳密なUnicodeプログラミングを行うには:

  • Unicodeは認識して(されている文字列のAPIのみを使用しないで strlenstrcpy、...しかし、彼らは、widestring対応をwstrlenwsstrcpy、...)
  • テキストのブロックを処理するときは、Unicode文字(utf-7、utf-8、utf-16、ucs-2、...)を失うことなく格納できるエンコーディングを使用してください。
  • OSのデフォルトの文字セットがUnicode互換であることを確認してください(例:utf-8)
  • Unicode互換のフォントを使用する(例:arial_unicode)

マルチバイト文字シーケンスは、UTF-16エンコーディング(通常はで使用されるエンコーディング)よりも前のエンコーディングです。 wchar_t Windowsのみであるように思われます。

聞いたことがないwint_t


wint_tは、wchar_tと同様に、<wchar.h>で定義されている型です。ワイド文字に関しては、intが「char」に関して持っているのと同じ役割を果たします。ワイド文字値またはWEOFを保持できます。
ジョナサンレフラー

3

最も重要なことは、テキストとバイナリデータ常に明確に区別することです。モデルに従うようにしてくださいPythonの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がUnicodeを表すためにそれを使用するかどうかは決定できません。

これは、私のコードがどこでもchar型を使用してはならず、wint_tとwchar_tを処理できる関数を使用する必要があることを意味しますか?

参照:

いいえ。UTF-8は、char*文字列を使用する完全に有効なUnicodeエンコーディングです。プログラムが非ASCIIバイトに対して透過的である場合(たとえば、\rおよびに作用する行末コンバーター)、\nが、そのまま他の文字を通過する)、あなたはすべての変更を加えないする必要があります!

UTF-8を使用する場合は、char=文字(toupperループで呼び出さないなど)またはすべての仮定を変更する必要があります。char画面列(テキストの折り返しなど)の。

UTF-32を使用すると、固定幅の文字が単純になります(ただし、固定幅の書記素は使用できません)。が、すべての文字列のタイプを変更する必要があります)。

UTF-16を使用する場合は、固定幅文字の仮定、このシングルバイトエンコーディングから最も困難なアップグレードパスになり、8ビットのコード単位の仮定を、。

クロスプラットフォームではないため、積極的に回避する wchar_tことをお勧めします。UTF-32の場合もあれば、UTF-16の場合もあり、Unicode以前の東アジアのエンコーディングである場合もあります。使用をお勧めしますtypedefs

さらに重要なのは、を避けることTCHARです。


それが不幸だとはまったく思いません-charはintです。それは利点です。リテラル文字定数の使用は、1つの使用法として思い浮かびます。そしてchar *const char *私が覚えている最後に渡された場合、aを取る関数は問題を抱えることがあります(しかし、私はこれについて漠然としていて、どの関数が塩のピンチでそれを取るので)。他の言語でより複雑だからといって、それが悪いデザインであるとは限りません。
プリフタン

2

私は標準ライブラリの実装を信用しません。独自のUnicodeタイプをロールするだけです。

#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をグーグルで検索してください。したがって、メモリ内にはマルチバイトはありません。1つwchar_tは1つの文字を表します。ただし、シリアル化に関しては、一部の文字が複数のバイトで表されるUTF-8のようなものにエンコードする必要がある場合です。

またstrcmp、ワイド文字列用に新しいバージョンなどを作成する必要がありますが、これは大きな問題ではありません。最大の問題は、char配列のみを受け入れるライブラリ/既存のコードとの相互運用です。

そしてそれは sizeof(wchar_t)(正しく実行したい場合は4バイトが必要になります)、必要に応じてtypedef/ macrohacksを使用していつでもより大きなサイズに再定義できます。


1

私の知る限り、wchar_tは実装に依存しています(このwikiの記事からわかるように)。そしてそれはユニコードではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.