web-dev-qa-db-ja.com

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

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

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

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

80
prinzdezibel

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

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

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

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

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

21
Hans van Eck

C99以前

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

その結果、Hans van Eckによって提案されたアプローチ(ICU-International Components for Unicode-library)の周りのラッパーを書くこと)は、健全なIMOです。

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

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

UTF-8、UTF-16、UTF-32形式(UTFはUnicode変換形式を表します- nicode を参照)の便利な点の1つは、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ビットWord)コードセットであり、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ユニバーサルキャラクター名

構文
ユニバーサル文字名:
_\u_hex-quad
_\U_hex-quad hex-quad
hex-quad:
16進数16進数16進数16進数16進数

7.28 Unicodeユーティリティ_<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>_は_size_t_を定義し、_<wchar.h>_は_mbstate_t_を定義し、_<stdint.h>_は_uint_least16_t_と_uint_least32_t_を定義します。 )_<uchar.h>_ヘッダーは、(再起動可能な)変換関数の最小セットも定義します。

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

_\unnnn_または_\U00nnnnnn_表記を使用して、識別子に使用できるUnicode文字に関するルールがあります。識別子内のそのような文字のサポートをアクティブにアクティブにする必要がある場合があります。たとえば、GCCでは、識別子でこれらを許可するために_-fextended-identifiers_が必要です。

MacOS Sierra(10.12.5)は、1つのプラットフォームに名前を付けると、_<uchar.h>_をサポートしないことに注意してください。

37

これ [〜#〜] faq [〜#〜] は豊富な情報です。そのページと Joel Spolskyによるこの記事 の間で、あなたは良いスタートを切るでしょう。

私は途中で出会った一つの結論:

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

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

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

10
dbyron

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

  • Unicode対応の文字列APIのみを使用してください([〜#〜] not [〜#〜]strlenstrcpy、...が、対応するワイド文字列wstrlenwsstrcpy、...)
  • テキストのブロックを扱うときは、Unicode文字(utf-7、utf-8、utf-16、ucs-2、...)を損失なしに保存できるエンコードを使用します。
  • OSのデフォルトの文字セットがUnicode互換であることを確認します(例:utf-8)
  • Unicode互換のフォントを使用します(例:arial_unicode)

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

wint_tについて聞いたことがありません。

7
sebastien

最も重要なことは、常にテキストとバイナリデータを明確に区別することですPython 3.x str vs. bytes またはSQL TEXT vs. BLOBのモデルに従うようにしてください。

残念ながら、Cは「ASCII文字」とint_least8_tの両方にcharを使用することで問題を混乱させています。あなたは次のようなことをしたいと思うでしょう:

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

UTF-16およびUTF-32コード単位のtypedefも必要になる場合がありますが、wchar_tのエンコードが定義されていないため、これはより複雑です。プリプロセッサ#ifsだけが必要です。 CおよびC++ 0xの便利なマクロは次のとおりです。

  • __STDC_UTF_16__ —定義されている場合、タイプ_Char16_tは存在し、UTF-16です。
  • __STDC_UTF_32__ —定義されている場合、タイプ_Char32_tは存在し、UTF-32です。
  • __STDC_ISO_10646__ —定義されている場合、wchar_tはUTF-32です。
  • _WIN32 — Windowsでは、wchar_tはUTF-16ですが、これは標準に違反しています。
  • WCHAR_MAXwchar_tのサイズを決定するために使用できますが、OSがそれを使用してUnicodeを表すかどうかはできません。

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

こちらもご覧ください:

いいえ。UTF-8は、char*文字列を使用する完全に有効なUnicodeエンコードです。プログラムが非ASCIIバイトに対して透過的である場合(たとえば、\rおよび\nで動作するが、他の文字を変更せずに渡す行末変換コンバーター)を作成する必要があるという利点があります。変更は一切ありません!

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

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

UTF-16を使用する場合、固定幅文字の仮定と8ビットコード単位の仮定の両方を破棄する必要があり、これが最も難しいシングルバイトエンコーディングからパスをアップグレードします。

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

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

3
dan04

標準ライブラリの実装は信用しません。独自の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
user2074102

基本的に、メモリ内の文字列を、charではなくwchar_t配列として扱います。何らかの種類のI/O(ファイルの読み取り/書き込みなど)を行う場合、UTF-8(これがおそらく最も一般的なエンコード)を使用してエンコード/デコードできます。 RFCをGoogleで検索するだけです。そのため、メモリ内ではマルチバイトになりません。 1つのwchar_tは1文字を表します。ただし、シリアル化を行う場合、いくつかの文字が複数バイトで表されるUTF-8のようなものにエンコードする必要があります。

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

また、sizeof(wchar_t)については(必要に応じて4バイトが必要になります)、必要に応じてtypedef/macro hacksを使用して常により大きなサイズに再定義できます。

2
Mike Weller

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

1
PolyThinker