ワイド文字とワイド文字列

こんにちは、めのんです!

みなさん本当にお久しぶりです。
ご機嫌いかがでしょうか?

年末は何かとバタバタしていてブログの更新が滞ってしまいました。
気がつけばもう大晦日です。
このままでは年が越せませんので何とか頑張って更新します。

今回の話題は「ワイド文字」と、ワイド文字の文字列である「ワイド文字列」です。

ワイド文字とwchar_t型

最初にワイド文字とそれを表すための型について簡単に見ていくことにします。

ワイド文字とは

まずはじめにワイド文字とは何なのかを解説しないといけません。
標準規格では次のように定義されています。

3.7.3 ワイド文字(wide character) 型wchar̲tのオブジェクトに納まり,その時点のロケールでの任意の文字を表現できるビット表現。

これだけ見ても何のことかわかりませんよね。

普通のchar型の配列で表現する文字列だと、日本語などの文字を表すには複数バイト必要になることがほとんどです(「多バイト文字といいます」)。
おそらくワイド文字狙っているところは、多バイト文字は文字列は扱いが大変なので、1文字をひとつの整数値で表せるようにすることなんじゃないかなと思います。

その狙い通りにうまくいったかというと決してそんなことはないんですけど、まだUnicodeも完成していなかった時代にできた概念ですからね。

Cでは、多バイト文字からワイド文字へ、逆にワイド文字から多バイト文字に変換することができるようになっています。
その変換のしかたはロケールに依存しています。
具体的にはLC_CTYPEカテゴリがそれにあたります。

この変換は常にできるわけじゃなくて、失敗することもあります。
たとえば、多バイト文字がシフトJISでワイド文字がUnicodeだとすれば、シフトJISに変換できないUnicodeは普通にありますよね。

wchar_t型

ワイド文字を表現する型として「wchar_t型」がいくつかのヘッダで定義されています。
wchar_t型が
Cのwhar_t型はtypedefで定義された別名なので、その実態は単なる整数型です。

wchar_t型がどんな型に定義されるかは完全に処理系定義で、最小の表現範囲はchar型と同じです(符号付きか符号無しかもchar型と同じです)。
最近の処理系では、16ビットの符号無し整数型または32ビットの符号付き整数型に定義されることが多いようです。

GCCの場合、Windowsとローエンドのマイコン用処理系を除けばwchar_t型は32ビットになるようです。
Windowsの処理系は、Visual C++やC++ Builder、ほかにMinGWやCygwinで使うGCCも含めて16ビットになるようです。

whar_t型のサイズは処理系定義ですが、内部表現も処理系定義です。
最近はUTF-16またはUTF-32がほとんどですが、UnicodeやISO 10646が制定されたのはCの最初の標準規格ができたあとのことです。

ベテランの技術者さんから聞いた話では、昔のC処理系(MS-DOSの処理系など)ではシフトJISの上位下位が16ビットに格納されていたとのことです。
現在でも、GCCでは-fwide-exec-charsetオプションでwchar_t型の内部表現を指定することができます。

こんな感じで、wchar_t型は移植性という意味ではイマイチなんですよね。

ワイド文字定数とワイド文字列リテラル

ワイド文字やワイド文字列は普通の文字や文字列とは違う型です。
ですから、普通に一重引用符や二重引用符で囲んだだけでは表すことができません。

ワイド文字定数とワイド文字列リテラルの記述方法

ではどうやって表現するかというと、一重引用符や二重引用符の直前に大文字の「L」を付けます。
たとえばこんな感です。

wchar_t wc = L'あ';  // ワイド文字定数
wchar_t wcs[] = L"こんにちは"; // ワイド文字列リテラル

Lと引用符の間に空白をいれてはいけませんし、Lは必ず大文字でないといけません。

ワイド文字列リテラルの連結

実はこれまで解説してこなかったんですけど、Cの文字列リテラルを並べて書くと連結することができます。
たとえばこんな感じです。

char s[] = "hello " "world";  // "hello world"と同じ

ワイド文字列リテラルでもこれと同じことができます。

ただし、C99より前はワイド文字列リテラルにあとに普通の文字列リテラルを並べた場合にどうなるのかがハッキリ決まっていませんでした。
ですので、以前のVisual C++だと(C99に対応していなかったので)うまくいかなかったと聞いています。

今はもうそういうことはないと思いますので、ワイド文字列リテラルのあとに(Lがない)文字列リテラルを並べても、ちゃんと連結されてひとつのワイド文字列になってくれます。

wchar_t wcs[] = L"こんにちは " "世界";  // L"こんにちは 世界"と同じ

ワイド文字やワイド文字列の操作

ここまででワイド文字やワイド文字列を定義することができるようになりました。
でも、それだけでは何の操作もできませんよね。
正確には操作はできますけど、全部自分で書かないといけなくなります。

まさかそんなことはなくて、Cにはちゃんとワイド文字やワイド文字列を操作するための関数が用意されています。

wctype.hヘッダ

ワイド文字とワイド文字列のためのヘッダとして、「wctype.h」ヘッダと「wchar.h」ヘッダが用意されています。

「wctype.h」ヘッダは「ctype.h」ヘッダに対応するもので、「is~」関数は「isw~」関数に、「to~」関数は「tow~」関数に対応しています。

「isw~」関数は引数として「wint_t型」を受け取り、int型を返します。
「wint_t型」というのは、wchar_t型の値とEOFに対応する「WEOF」を格納できる型です。
「tow~」関数は引数としてwint_t型を受け取り、wint_t型を返します。

ほかにもiswctype関数、towtrans関数、wctype関数、wtrans関数というのもあるのですが、ここでは割愛します。
ご興味のある方はご自身で調べてみてください。

wchar.hヘッダ

「wchar.h」ヘッダはwctype.hで定義しているもの以外の関数が全部詰め込まれています。
文字や文字列を扱う関数はすべて対応するwchar_t型版があるわけではなく、対応するものがあったとしても微妙に仕様が対称になっていないことがあるのでご注意ください。

普通の文字列のための「str~」で始まる関数は、基本的に「wcs~」で始まる関数が対応しています。
wcstok関数は仕様が対称になっていないのでご注意ください。

「mem~」で始まる関数(memcpyなど)は「wmem~」で始まる関数が対応していて、voidへのポインタではなくwchar_tへのポインタが対象になります。

「~char」で終わるgetchar関数などは「~wchar」で終わるgetwchar関数などが対応しています。
同様に「~c」で終わるgetc関数などは「~wc」で終わるgetwc関数などが対応しています。

ほかにもいろいろありますが、こんな感じでほとんどの関数には対応するものがあります。
すべてを挙げるとキリが無いので、必要に応じてリファレンスで調べてみてください。

printf関数やscanf関数での書式化

printf関数やscanf関数にもwchar_t型版のwprintf関数やwscanf関数というのがあります。
どちらの場合でも、ワイド文字やワイド文字列を書式化して入出力するための書式があります。

それらの関数で書式化する場合には、「%lc」や「%ls」のように小文字の「l(エル)」を指定する必要があります。
「l」を付けなければ、たとえwprintf関数やwscanf関数であっても、普通の文字や文字列として扱われてしまいます。

ここでの変換にもロケールが関わってきますので、必要な場合はsetlocale関数をあらかじめ呼び出してロケールを設定しておいてください。

多バイト文字(列)とワイド文字(列)の相互変換

多バイト文字や多バイト文字列からワイド文字やワイド文字列への変換、あるいはその逆を行うための関数も用意されています。

一番基本的なものは最初期のC規格から用意されていて、「stdlib.h」ヘッダで宣言されています。
ここでは関数名を挙げるだけにしますので、必要な方はリファレンスを参照してください。

  • mbtowc関数
  • wctomb関数
  • mbstowcs関数
  • wcstombs関数

名前を見ればどの関数が何をする関数か大体わかると思います。
そんなに難しい関数ではありませんので、リファレンスを読めば使い方はわかると思います。

これらとよく似た関数が「wchar.h」ヘッダでも宣言されています。
「mbrtowc」関数のように途中に「r」が追加された名前になっています。

この「r」付きの関数はシフト状態を保持することができるもので、ISO-2022-JPなどを使うときには必要になるのだと思いますが、普段はほとんど用がないように思います(私の理解不足の可能性もあります)。

ほかにはsprintf関数やsscanf関数を使って「%lc」や「%ls」で変換することもできますね。

どんな方法で変換するにしてもロケールの設定は不可欠です。
使用する前に必ずsetlocale関数でロケールを設定しておくようにしてください。


駆け足でしたがワイド文字とワイド文字列の解説を行いました。

このブログの更新はこれで今年最後になります。
「PHPプログラマーのためのC講座」に関してもこれでいったん区切りとさせていただきます。
今後は不定期で継続していく可能性はあります。

年明けからはまた新たな企画を考えています。
ほかのサイトではあまりやっていない内容だと思いますので、どうぞご期待ください。

それではみなさん、よいお年をお迎えください!