国際化 - Unicode と文字列クラス

国際化の話、第 2 弾ということで Unicode とその限界、それと C++, Ruby での文字列の持ち方について書いてみたいと思います。

Unicode


国際化といえば Unicode なのですが、 登場までの流れを少し説明したいと思います。
C では文字列は基本は 1 バイトの char 型の配列です。
ただ、 1 バイトだと最大で 255 (0 は文字の末端として使います) 種類しか区別ができません。 英語だとそれで十分なのですが、日本語だとアルファベットもあわせるとひらがなぐらいしか入りません。 昔は文字の大きさもアルファベットに合わせる必要があったため、 ひらがなは表示が難しいので、カタカナにして、日本語はカタカナだけ使えるという時代がありました。 この名残が半角カタカナです。
英語などの文字の少ない言語しか対応できないようではだめだってことで、C では日本語などを入れるように 2 バイトの wchar_t 型を作って 日本語などは wchar_t 型の配列(ワイド文字列)に入れたらということになりました。 しかし、英語用に char 型できていたプログラムを wchar_t に変えるのは変更が大きすぎます。

そこで char 型の配列にいれるために 1 文字を複数のバイトで表すマルチバイト文字列が使われることになります。 これが Windows(MS-DOS)ではシフト JIS で、 Un*x では EUC です。あと JIS(ISO-2022-JP)もあります。
シフト JIS 、 EUC はそれぞれの種類の OS で使われているものですが、なぜ JIS があるかというと通信用で 7 bit の文字コードが必要だったためです。 英語は 255 種類も必要なく、 1 bit 減らした 125 種類で十分なので、 ASCII コードは 7 bit しか使いません。 昔は 1 ビットでも通信量を減らしたいということがあり、 8 ビット目を切り落として、詰めてデータを送る通信方式がありました。 このため 7 bit の文字コードが必要となっていました。 この JIS コードは 7 bit に切り詰めている分、かなり使いづらいコードなので、プログラム中では、シフト JIS や EUC を使って、通信する前に JIS コードに変えるというように使われていました。しかし、最近ではそんな方式は使わないようになっているので、 JIS コードはほとんど見かけなくなりました。

修正を減らすためのマルチバイト文字列だったのですが、これはこれで扱いが難しいです。
よくある問題が漢字を含む Windows のパスでパス区切りの \ を探そうとすると 2 バイト構成されている漢字の中の 1 バイトに \ がひっかかって ちゃんと検索できないといったものです。 EUC はシフト JIS より後発のため、多少改善されてますが、 同じような問題はあります。

シェアを広げるためには、国際化は必須です。 しかし、やっぱり 1 文字は 1 つのデータとして扱わないとプログラミングのコストがかかります。
そこで Microsoft をはじめ、大手企業が乗り出してきて、登場したのが Unicode です。 これで 1 文字を 2 バイトに格納する仕様が決まり、最初の wchar_t に脚光がもどってきました。

UTF-8


文字を 2 バイトで扱うようにしたことによって、新たな問題も生じます。 2 バイトだとファイルや通信などで OS をまたぐ際にはエンディアンを考慮する必要が出てきます。
Unicode をそのままファイル落とすとビックエンディアンかリトルエンディアンか分からないので、 先頭にエンディアンを区別のための BOM を付けたものが UTF-16 で UTF-16BE と UTF-16LE があります。
ただ、これをテキストファイルの標準にしようとすると今までのプログラムをほとんど変える必要があります。 英語だけの文字の場合には ASCII コードと同じものとして扱えるようにしたものが UTF-8 です。英語を優遇した結果どこかにしわ寄せがくるもので、日本語などは今まで 2 バイトだったものが文字によって 3, 4 バイト使うことになります。

いまだに 7 bit 通信を考慮した UTF-7 や日本語のデータ量をそんなに増やさずにすむ UTF-9 といった規格もあるようですが、使われていないようです。

日本語を保存したときのデータ量が増えるとはいえ、 Unicode を使うと英語などの言語は 文字のメモリ使用量が 2 倍になるんだし、それに比べれば UTF-8 が使われていくのは、仕方ないかなと思います。
しかし、本来 1 バイトのため、エンディアンを考慮する必要のない UTF-8 ですが、 UTF-8 を使っているという識別のために BOM 付きの UTF-8 というものがあります。
しかも今は BOM 付きだと動かないものと BOM なしだとちゃんと認識してくれないものが混在していて、なんとかしてよという気がします。


Un*x


Unicode と UTF-8 の説明が終わったところで、実際にプログラムではどういった感じで文字列データを持っているのか という話に移りたいと思います。

QtQString という文字列クラスを使っていて、これには 2 バイトの Unicode が格納されています。 ファイルなどの外部から文字を取り込むときに Unicode にして、プログラム中では Unicode で処理するという一番、一般的なタイプだと思います。
Java や Perl などもこのタイプです。

GLib と glib を使っている GTK+ は、プログラム中は Unicode ではなく、 UTF-8 を使います。
文字列を取り込むときに UTF-8 に変換して、各関数は UTF-8 のマルチバイト処理だけ対応している感じです。データの型は一応 GString という文字列を格納する型はあるのですが、 全部これに入れてから使うという感じでもなく、 C の char 型配列が主に使われています。

Motif はというと、私が知らないだけかも知れませんが、 Unicode で統一しようという動きはなく、 いまだに EUC が使われている気がします。

Windows


Windows は .NET 以前は複雑です。
コンパイル時に _UNICODE の定義があるかないかで char と wchar_t が切り替わる TCHAR 型というものを使っています。
文字と文字列リテラルはワイド文字列の場合、前に L を付けるのですが、これも _UNICODE で切り替わるようにリテラルは _T のマクロで囲む必要があります。
char *str = "Hello";
wchar_t *wstr = L"Hello";
TCHAR *tstr = _T("Hello");
また、 MFC には CString という文字列クラスがあるのですが、 これも中に格納するデータは定義で char と wchar_t が切り替わっています。
さらに COM を使うということになると BSTR というデータ構造を使う必要があります。

.NET では文字列は System::String クラスで、これは 2 バイトの Unicode が格納されます。
ただ、これも値を変更できない変わったクラスではあります。

ちなみに Qt でも文字列リテラルを囲む _T は使用します。
MFC からの移植を簡単にするため、同じ名前にしたのでしょうが、 MFC の _T とは別ものです。 これは GetText の N_ や _ の役割と文字列リテラル(char *)を QString に変える役割をもっています。

C++ 標準ライブラリ


C++ の標準ライブラリでは std::string とワイド文字列用の std::wstring があります。 これはテンプレートでクラスで定義されたものに char と wchar_t をテンプレートの引数に渡した型を typedef したものです。 string と iostream はテンプレートを使って実装されていますが、 テンプレートとして使っているわけでは無いので、標準ライブラリではありますが、 STL には含まれないそうです。

string は C++ での char 配列の代わりとしてよく使われますが、 wstring は使われる場面は限られます。
GLib, Qt, MFC, .NET が使えない環境で日本語の文字列処理が必要な場合です。
それってどこだよという感じですが、私はクロスプラットホームで動作する コマンドラインプログラムを作ったときに使ったことがあります。 (Qt はラインセンスの関係で使いませんでした)

Unicode の限界


広く使われるようになった Unicode ですが、実は問題を含んでいます。
それは世界で使われてる文字を入れるのに 2 バイトでは足りないということです。
32 bit の大部分を使っているのは、中国、台湾、日本の漢字圏です。 そこで、 3 つの国で似ている漢字を一緒にしちゃえってことで、 無理やり 2 バイトに押し込めました。
(アルファベットに関しては、フォントで分けるべきものじゃないかというのまで、分けられているのですが...)
それでも、日本語だけ使うのであれば、特に問題はありません。
問題は日本語と中国語などを同時に使う場合です。 Unicode を使っていると 一部の文字がどちらの言語か判別できず、正確に表示されないということになってしまいます。 つまり、 Unicode では国際化はできるけど、多言語化はできません

4 バイトの Unicode を使えばいいのですが、さすがに文字データが 4 倍のメモリを使用するのは、なかなか採用されないでしょう。
多言語化はされないのか、多言語化したとき漢字が少し変になるぐらいかまわないと思われているのか 分かりませんが、世の中はどんどん 2 バイトの Unicode で統一されていっています。

Ruby の多言語化


そんな中、日本発のプログラミング言語はやっぱり違います。
Ruby はちゃんと多言語化に対応してしまいました。
標準に採用されている XML パーサーや Rails が UTF-8 で統一されているので、 GLib のような UTF-8 で統一する方針なのかなと思いきや、 Version 1.9 で多言語化(Multilingualization, M17N)が導入されました。

どうやっているかというと、 文字列クラス String のインスタンス 1 つ 1 つで自分が今どの文字コードなのか という文字コード情報を持っています。検索などの文字列処理関数は文字列のその情報をみて、 それにあった方式で処理することになります。
この方式では 1 文字で 1 単位というわけではないので、マルチバイト文字と同じ問題が発生するため C のようにポインタでまわして自分で中を見ていくということはできず、文字列には必ず文字列処理用の関数を通して扱う必要があります。
ただ、 Ruby ではもともと EUC やシフト JIS に対応していて、 C と違ってすでにずっとそういう方針だったので、特に違和感はありません。

また、文字コード情報は 8 バイト程度でしょうから、それが付いているだけで、 ASCII 文字では 1 バイト、日本語も 2 バイトと文字データのメモリ使用量が減ります。

Ruby の多言語化は素晴らしいのは素晴らしいのですが、 スクリプトの結果を表示するターミナルや GUI が Unicode で統一されていっているので、 結局、ちゃんと表示できるようにはならないでしょう。
Ruby の GUI ライブラリはいろいろあるのですが、基本的に C++ のライブラリを Ruby で使えるようにしたものなので、 XLib や WIN32API からごりごり作った Ruby 用のクロスプラットホームで使える GUI ツールキットができたらいいなと思います。
これは Ruby アソシエーションが開発プロジェクトを公募しているとき、やろうかとちょっと頭をよぎったのですが、 個人で 1 年でできるようなレベルのものじゃないなということでやめました。

関連記事
スポンサーサイト
Prev.    Category    Next 

Facebook コメント


コメント

コメントの投稿

Font & Icon
非公開コメント

このページをシェア
アクセスカウンター
アクセスランキング
[ジャンルランキング]
コンピュータ
40位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
4位
アクセスランキングを見る>>
カレンダー(アーカイブ)
プルダウン 降順 昇順 年別

03月 | 2017年04月 | 05月
- - - - - - 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 - - - - - -


はてな新着記事
はてな人気記事
ブロとも申請フォーム
プロフィール

yohshiy

Author:yohshiy
職業プログラマー。
仕事は主に C++ ですが、軽い言語マニアなので、色々使っています。

はてブ:yohshiy のブックマーク
Twitter:@yohshiy

サイト紹介
プログラミング好きのブログです。プログラミング関連の話題や公開ソフトの開発記などを雑多に書いてます。ただ、たまに英語やネット系の話になることも。