ワイド文字 | Programming Place Plus C言語編 第47章

トップページC言語編

このページの概要

以下は目次です。


ワイド文字

前章で説明したマルチバイト文字のほかにもう1つ、ワイド文字 (wide character) というものがあります。

ワイド文字は、環境が対応しているすべての文字が表現できます。そのため、複数の言語(プログラミング言語ではなく、日本語、英語などのこと)を扱うようなプログラムでは、ワイド文字を使うことがあります。

マルチバイト文字を char型で表現するのに対し、ワイド文字は wchar_t型で表現します。

wchar_t型は、char型のようにいつでも使える型ではなく、標準ライブラリの中で typedef による別名として定義されています。<stddef.h>、<stdlib.h>、<wchar.h> といった各標準ヘッダで定義されています。

【上級】C++ では、wchar_t はキーワードです。

wchar_t型は、環境でサポートされているすべてのロケールの中で、もっとも大きい文字を表現できる大きさを持つ整数型です。つまり、規格としては具体的な大きさが決められていません。

なお、ワイド文字やワイド文字列のリテラルには、L というプリフィックスを付加して、L’x’ や L“abc” のように表します

ワイド文字定数の型は wchar_t型、ワイド文字列リテラルの型は wchar_t の配列型です。ワイド文字列リテラルの末尾には、ワイド文字版のヌル文字(L’\0’) があります。

以下は、ここまでの内容を確認する簡単なプログラムです。

#include <stdio.h>
#include <wchar.h>

int main(void)
{
    wchar_t ws[] = L"abcde";

    printf("%zu\n", sizeof(wchar_t));
    printf("%zu\n", sizeof(ws));
    printf("%zu\n", sizeof(L'x'));
}

実行結果:

2
12
2

wchar_t型の大きさを調べており、2 という結果を得られていますが、前述したとおり、大きさは異なる可能性があります。たとえば、clang で試すと 4 になります。

ワイド文字列の末尾には、ワイド文字版のヌル文字 (L’\0’) があります。このヌル文字の大きさも、sizeof(wchar_t) ですから、L“abcde” の全体の大きさは、2*(5+1) で 12 です。

ワイド文字が、具体的にどのような文字コードで表現されるかについては処理系定義ですが、Unicode が使われていることが多いです。

Unicode は、扱える文字の一覧表と、それぞれの文字にどんな値を割り当てるのかを定めた規格です。後者の「どんな値を割り当てるのか」という部分を、符号化方式(エンコーディング方式) (encoding method) といい、この種類として、UTF-8UTF-16UTF-32 といったものがあります。

ですから、単に「Unicode が使われている」と言われただけでは、文字に割り当てられる具体的な値は分かりません。

C言語編では基本的に Visual Studio をベースとしており、ここで使われている UTF-16 を前提に説明しています。

UTF-16 の「16」は 16ビットのことで、1文字を 16ビットの単位で表現します。「単位で」というのがポイントで、実際には、16ビットか、それを2つ組み合わせた 32ビットかのいずれかで表現します。

Shift_JIS のときと考え方は同じで(第46章)、最初の 16ビットの部分が “特定の範囲” の値だったときにだけ、後続の 16ビットをペアとみなすようにルール化されています。

【上級】2つの 16ビットの値で 1文字を表現するパターンを、サロゲートペア (surrogate pair) といいます。これは UTF-16 が規格化された当初はなかった仕様です。Unicode で扱う文字種が増えるにつれて、16ビットでは表現できなくなってしまい、やむを得ずなされた対処でした。

UTF-16 を使う Visual Studio で、sizeof(wchar_t) が 2 になるのは、1文字を 16ビット単位で扱うことに由来しています。しかし実際には、32ビット必要なことがあるので、「環境でサポートされているすべてのロケールの中で、もっとも大きい文字を表現できる大きさを持つ整数型」という wchar_t型の定義と合致していないように思えます。残念ながらこれはそういうものです。

【上級】サロゲートペアの仕様がなかったころに 16ビットと定めたので、互換性を維持するために変更できなくなってしまったようです。

どんな文字を使うかは、そのプログラムで扱う文字データ次第ですから何とも言えませんが、UTF-16 で 32ビットを使う文字の出現頻度はそれほど高くありません。そのため、UTF-16 の 1文字がつねに 16ビットであるという前提で書かれているプログラムがよくあるのですが、そのような想定は正しくありません。32ビットの文字が出現したときに、異常な動作を起こすことがあります。

ワイド文字では、1文字の大きさが固定的であるという説明がなされることがよくあります。意味的には正しいのですが、UTF-16 の仕組みが示しているように、実はそのような保証はないかもしれません。それでも少なくとも、wchar_t型の大きさは固定的です(環境によって異なりますが、同じ環境下では同じ大きさ)。

【C11】ワイド文字を表現する新たな2つの型、char16_tchar32_t が加わりました。いずれも、<uchar.h> という標準ヘッダで定義されています。char16_t は 1文字を 16ビットで、char32_t は 32ビットで表現します。通常はそれぞれ UTF-16、UTF-32 によって表現される文字を扱います。文字定数や文字列リテラルに uプリフィックスを付加すると char16_t型に、Uプリフィックスを付加すると char32_t型になります。

ワイド文字を扱う標準ライブラリ関数

<string.h> に定義されている各種の文字列操作関数のワイド文字列版があります。これらの関数は、関数名の “str” の部分を “wcs” に置き換えた名前で統一されています。これらのワイド文字列版は、<wchar.h> に定義されています。

たとえば、strcpy関数に対応するワイド文字列版は wcscpy関数strcmp関数ならば wcscmp関数という具合です。

少し試してみましょう。

#include <stdio.h>
#include <wchar.h>

int main(void)
{
    wchar_t* wstr1 = L"wide string";
    wchar_t wstr2[] = L"ワイド文字列による日本語のテスト";

    wprintf(L"len: %zu\n", wcslen(wstr1));
    wprintf(L"len: %zu\n", wcslen(wstr2));

    wprintf(L"sizeof: %zu\n", sizeof(wstr1));
    wprintf(L"sizeof: %zu\n", sizeof(wstr2));

    if (wcscmp(wstr1, wstr2) != 0) {
        wcscpy(wstr2, wstr1);
    }

    wprintf(L"%ls\n", wstr1);
    wprintf(L"%ls\n", wstr2);
}

実行結果

len: 11
len: 16
sizeof: 4
sizeof: 34
wide string
wide string

wcslen関数は、ワイド文字列の文字数を返します。ワイド文字列における文字数は、バイト数とは意味が異なることに注意しなければなりません。

大きさ(バイト数)が必要であれば sizeof演算子を使う必要があります。自分が必要としているものが、文字数なのか大きさなのか、またヌル文字を含むのか含まないのか、しっかり意識してください。

ワイド文字列の出力には、wprintf関数を使っています。printf関数のワイド文字列版です。

wprintf関数を使ってワイド文字列を出力する際には、“%s” 変換指定子に、“l” 変換修飾子を挟み込み、L“%ls” と書きます。 “%s” を使ってしまうと、渡された文字列がマルチバイト文字列であるとみなされ、それをワイド文字列へ変換して出力しようとします。実引数に指定しているものがワイド文字列なのであれば、L“%ls” を使わないといけません。

wcscmp関数、wcscpy関数の使い方はシンプルで、特に迷うことはないと思います。

マルチバイト文字をワイド文字に変換する

マルチバイト文字をワイド文字へ変換するには、mbtowc関数を使います。mbtowc関数は、<stdlib.h> に以下のように宣言されています。

int mbtowc(wchar_t* restrict pwc, const char* restrict s, size_t n);

restrict については、第57章で取り上げます。動作に影響はないので、今は無視して問題ありません。

第1引数に、変換されたワイド文字を受け取るポインタ変数を指定します。

第2引数に、変換元となるマルチバイト文字を指すポインタを指定します。マルチバイト文字を構成しているバイト数は分からないので、先頭のバイトを指すポインタを渡さなければなりません。

第3引数には、マルチバイト文字の大きさの最大値を指定します。前章でも使った MB_CUR_MAX を指定することが多いでしょう。

戻り値は、変換に成功したら、マルチバイト文字を構成していたバイト数が返されます。マルチバイト文字として不正なバイト列だった場合など、エラーが発生したときには -1 が返されます。

この関数は、ロケールの LC_CTYPE の設定値による影響を受けることに注意してください(第46章)。

では、実際に試してみます。

#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <locale.h>

int main(void)
{
    const char mb[] = "多";

    // LC_CTYPE をネイティブロケールに変更
    if (setlocale(LC_CTYPE, "") == NULL) {
        fputs("ロケールの設定に失敗しました。\n", stderr);
        return EXIT_FAILURE;
    }

    wchar_t wc;
    if (mbtowc(&wc, mb, MB_CUR_MAX) == -1) {
        fputs("ワイド文字への変換に失敗しました。\n", stderr);
        return EXIT_FAILURE;
    }

    wprintf(L"%lc\n", wc);
}

実行結果

マルチバイト文字の ‘多’ をワイド文字に変換しています。‘多’ は 1文字ですが、1バイトで表現できるわけではないので、char型の配列として扱います。

mbtowc関数はロケールの影響を受けます。これは、マルチバイト文字がどんな文字コードで表現されているかに応じて、関数内の処理を変えなければならないためです。setlocale関数で LC_CTYPE の設定値を、ネイティブロケールに変更しておかないと、“C”ロケールのままになっているので、恐らく ASCIIコード扱いで処理が行われてしまいます。この辺りは、前章で解説したとおりです。

mbtowc関数の戻り値はきちんと調べるようにしましょう。マルチバイト文字とワイド文字の変換は、文字コードの変換ということでもあります。プログラム内でどんな文字が使われ、それが両方の文字コードで表現できることを知っていない限り、変換できることをアテにはできません。特に、外部から入力を受けて、その文字を変換するようなプログラムでは、変換できない可能性をつねに考慮しなければなりません。

ワイド文字をマルチバイト文字に変換する

ワイド文字をマルチバイト文字へ変換するには、wctomb関数を使います。wctomb関数は、<stdlib.h> に以下のように宣言されています。

int wctomb(char* s, wchar_t wc);

第1引数に、変換されたマルチバイト文字を受け取るポインタ変数を指定します。

第2引数に、変換元となるワイド文字を指定します。mbtowc関数と違って、ワイド文字の1文字は、wchar_t型の大きさに収まることが保証されているので、こちらはポインタではありませんし、第3引数も必要ありません。

戻り値は、変換に成功したら、マルチバイト文字を構成するバイト数が返されます。ワイド文字がマルチバイト文字で表現できない場合など、エラーが発生したときには -1 が返されます。

この関数は、ロケールの LC_CTYPE の設定値による影響を受けることに注意してください(第46章)。

では、実際に試してみます。前の項のサンプルプログラムの逆で、ワイド文字をマルチバイト文字へ変換しています。

#include <limits.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

int main(void)
{
    const wchar_t wc = L'多';

    // LC_CTYPE をネイティブロケールに変更
    if (setlocale(LC_CTYPE, "") == NULL) {
        fputs("ロケールの設定に失敗しました。\n", stderr);
        return EXIT_FAILURE;
    }

    char mb[MB_LEN_MAX + 1];  // 全ロケールで可能性がある最大の大きさ + 末尾の '\0'
    int len = wctomb(mb, wc);
    if (len == -1) {
        fputs("マルチバイト文字への変換に失敗しました。\n", stderr);
        return EXIT_FAILURE;
    }

    mb[len] = '\0';
    printf("%s\n", mb);
}

実行結果

ワイド文字の ‘多’ をマルチバイト文字に変換しています。

変換後のマルチバイト文字が何バイトになるか分からないので、char型の配列を用意しています。このとき、要素数に、MB_LEN_MAXマクロを使っています。MB_LEN_MAXマクロは、環境が対応しているすべてのロケールの中で、もっとも大きな文字の大きさに置換されます。MB_LEN_MAX は、limits.h に定義されています。

配列を宣言する時点では setlocale関数で LC_CTYPE を変更していませんから、現在のロケールに応じた結果を返してしまう MB_CUR_MAXマクロを使うことは正しくありません。

wctomb関数も、mbtowc関数と同様にロケールの LC_CTYPE の影響を受けます。デフォルトの “C”ロケールのままにしていると、恐らく ASCIIコードへ変換しようとしますから、変換できずに失敗することでしょう。ここでは、実行環境が使っている Shift_JIS などの文字コードを期待していますから、ネイティブロケールに変換しておきます。

wctomb関数の変換結果は char型の配列ですが、あくまでも1文字を変換する関数なので、ヌル文字を付加しないことに注意してください。変換結果を文字列として扱うことがあるのなら、ヌル文字を補うか、あらかじめ配列を 0 で埋めておくかしないといけません。

マルチバイト文字列をワイド文字列に変換する

今度は、文字列を変換してみます。

マルチバイト文字列をワイド文字列へ変換するには、mbstowcs関数を使います。mbstowcs関数は、<stdlib.h> に以下のように宣言されています。

size_t mbstowcs(wchar_t* restrict pwcs, const char* restrict s, size_t n);

restrict については、第57章で取り上げます。動作に影響はないので、今は無視して問題ありません。

第1引数に、変換されたワイド文字列を受け取るポインタ変数を指定します。

第2引数に、変換元となるマルチバイト文字列を指すポインタを指定します。

第3引数には、変換するワイド文字列の要素数を指定します。つまり、変換結果として何文字のワイド文字列を要求しているかという指定です。マルチバイト文字列の文字数が、指定した文字数に満たない場合は、末尾の文字まで変換を行います。

戻り値は、変換に成功したら、得られたワイド文字列の要素数が返されます。マルチバイト文字として不正な文字を含んでいた場合など、エラーが発生したときには -1 が返されます。

この関数は、ロケールの LC_CTYPE の設定値による影響を受けることに注意してください(第46章)。

では、実際に試してみます。

#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <locale.h>

int main(void)
{
    const char mbs[] = "ワイド文字列への変換実験";

    // LC_CTYPE をネイティブロケールに変更
    if (setlocale(LC_CTYPE, "") == NULL) {
        fputs("ロケールの設定に失敗しました。\n", stderr);
        return EXIT_FAILURE;
    }

    wchar_t wcs[32];
    if (mbstowcs(wcs, mbs, sizeof(wcs)/sizeof(wcs[0])) == -1) {
        fputs("ワイド文字列への変換に失敗しました。\n", stderr);
        return EXIT_FAILURE;
    }

    wprintf(L"%ls\n", wcs);
}

実行結果

ワイド文字列への変換実験

変換後のワイド文字列の文字数は、マルチバイト文字列の文字数と一致するはずですが、そもそも、マルチバイト文字列の文字数を得ること自体が簡単ではありません。前章で見たとおり、mblen関数を駆使してカウントする必要があります。

ここでは手抜きをして、32文字の固定長の配列を用意していますが、プログラムの内容次第では、きちんと計算しなければならないかもしれません。あるいは可能性がある上限値が分かっているのなら、その値を使えばよいでしょう。いずれにしても、ヌル文字の分を忘れないようにしてください。

ワイド文字列をマルチバイト文字列に変換する

ワイド文字列をマルチバイト文字列へ変換するには、wcstombs関数を使います。

wcstombs関数は、<stdlib.h> に以下のように宣言されています。

size_t wcstombs(char* restrict s, const wchar_t* restrict pwcs, size_t n);

restrict については、第57章で取り上げます。動作に影響はないので、今は無視して問題ありません。

第1引数に、変換されたマルチバイト文字列を受け取るポインタ変数を指定します。

第2引数に、変換元となるワイド文字列を指すポインタを指定します。

第3引数には、変換するマルチバイト文字列の大きさの合計を指定します。つまり、変換結果として何バイト分のマルチバイト文字列を要求しているかという指定です。ワイド文字列の文字数が、指定したバイト数の文字を表現するのに満たない場合は、末尾の文字まで変換を行います。

戻り値は、変換に成功したら、得られたマルチバイト文字列の大きさの合計が返されます。マルチバイト文字で表現できない文字を含んでいた場合など、エラーが発生したときには -1 が返されます。

この関数は、ロケールの LC_CTYPE の設定値による影響を受けることに注意してください(第46章)。

では、実際に試してみます。

#include <limits.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

int main(void)
{
    const wchar_t wcs[] = L"マルチバイト文字列への変換実験";

    // LC_CTYPE をネイティブロケールに変更
    if (setlocale(LC_CTYPE, "") == NULL) {
        fputs("ロケールの設定に失敗しました。\n", stderr);
        return EXIT_FAILURE;
    }

    char mbs[(sizeof(wcs) / sizeof(wcs[0])) * MB_LEN_MAX];
    if (wcstombs(mbs, wcs, sizeof(mbs)) == -1) {
        fputs("マルチバイト文字列への変換に失敗しました。\n", stderr);
        return EXIT_FAILURE;
    }

    printf("%s\n", mbs);
}

実行結果

マルチバイト文字列への変換実験

変換結果を受け取る配列の要素数が複雑な指定になっています。文字数は、マルチバイト文字列でもワイド文字列でも変わらないはずですから、まずはワイド文字列の文字数を調べる必要があります。これは、いつものように sizeof演算子を使って配列の要素数を得ればよいです。

文字数が分かったら、そこに MB_LEN_MAX の値を掛け合わせて、実際に使用する可能性がある最大のバイト数にします。


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

マルチバイトの文字列リテラルと、ワイド文字列リテラルとの連結結果は、両者を合体させたワイド文字列リテラルです。

#include <stdio.h>
#include <wchar.h>

int main(void)
{
    const wchar_t str[] = "abc" L"あいうえお";  // ワイド文字列リテラルになる

    wprintf(L"%ls\n", str);
}

実行結果:

abcあいうえお

【C11】C11 には u8、u、U の各プリフィックスが追加されているため、組み合わせのパターンが多くなっています。プリフィックスを持たない文字列リテラルと、プリフィックスを持つ文字列リテラルは連結することができ、プリフィックスがある方に合わせられます。u8プリフィックスが付加された文字列リテラルと、ほかのプリフィックスが付加された文字列リテラルの連結はできません。異なるプリフィックスを持つワイド文字列リテラル同士の連結が可能かどうか、また可能であればどのような結果になるのかは処理系定義です。


練習問題

問題① マルチバイト文字とワイド文字の違いを説明してください。

問題② L“abcde” の大きさを調べたところ 24バイトでした。L’\0’ は何バイトですか?

問題③ 2つのワイド文字列を用意し、互いの内容が一致しているかどうかを調べるプログラムを作成してください。wcscmp関数を使うものと、使わずに自力で行うものを両方作成してください。

問題④ 1つのワイド文字列と、1つのマルチバイト文字列を用意し、互いの内容が一致しているかどうかを調べるプログラムを作成してください。


解答ページはこちら

参考リンク


更新履歴

≪さらに古い更新履歴を展開する≫



前の章へ (第46章 マルチバイト文字)

次の章へ (第48章 数学関数)

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る