C言語編 第47章 ワイド文字

先頭へ戻る

この章の概要

この章の概要です。

ワイド文字

前章で説明したマルチバイト文字のほかにもう1つ、ワイド文字というものがあります。ワイド文字は、現在のロケールに存在するすべての文字が表現できる文字表現です。そのため、複数の言語(プログラミング言語ではなく、日本語、英語などのこと)を扱うようなプログラムでは、ワイド文字を使うことがあります。

マルチバイト文字を 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( "%u\n", sizeof(wchar_t) );
    printf( "%u\n", sizeof(ws) );
    printf( "%u\n", sizeof(L'x') );
    
    return 0;
}

実行結果:

2
12
2

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

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


ワイド文字が、具体的にどのような文字コードで表現されるかについてはコンパイラごとに異なりますが、Unicode が使われていることが多いです。Unicode は、扱える文字の一覧表と、それぞれの文字にどんな値を割り当てるのかを定めた規格です。後者の「どんな値を割り当てるのか」という部分を、符号化方式(エンコーディング方式)といい、この種類として、UTF-8UTF-16UTF-32 といったものがあります。

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

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

UTF-16 の「16」は 16ビットのことで、1文字を 16ビットの単位で表現します。 「単位で」というのがポイントで、実際には、16ビットか、それを2つ組み合わせた 32ビットかのいずれかで表現します。Shift_JIS のときと考え方は同じで(第46章)、最初の 16ビットの部分が "特定の範囲" の値だったときにだけ、後続の 16ビットをペアとみなすようにルール化されています。

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

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

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

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

ワイド文字では、1文字の大きさが固定的であるという説明がなされることが非常に多いのですが、UTF-16 の仕組みが示しているように、そのような保証はありません。固定的なのは、wchar_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: %u\n", wcslen(wstr1) );
    wprintf( L"len: %u\n", wcslen(wstr2) );

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

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

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

    return 0;
}

実行結果

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* pwc, const char* s, size_t n);

第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[] = "多";
    wchar_t wc;

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

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

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

    return 0;
}

実行結果

マルチバイト文字の '多' をワイド文字に変換しています。 '多' は 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 <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <locale.h>

int main(void)
{
    const wchar_t wc = L'多';
    char mb[MB_LEN_MAX + 1];  /* 全ロケールで可能性がある最大の大きさ + 末尾の '\0' */
    int len;

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

    len = wctomb( mb, wc );
    if( len == -1 ){
        fputs( "マルチバイト文字への変換に失敗しました。\n", stderr );
        return EXIT_FAILURE;
    }

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

    return 0;
}

実行結果

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

変換後のマルチバイト文字が何バイトになるか分からないので、char型の配列を用意しています。このとき、要素数に、MB_LEN_MAXマクロを使っています。MB_LEN_MAXマクロは、環境が対応している全てのロケールの中で、最も大きな文字の大きさに置換されます。
配列を宣言する時点では 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* pwcs, const char* s, size_t n);

第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[] = "ワイド文字列への変換実験";
    wchar_t wcs[32];

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

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

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

    return 0;
}

実行結果

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

変換後のワイド文字列の文字数は、マルチバイト文字列の文字数と一致するはずですが、そもそも、マルチバイト文字列の文字数を得ること自体が簡単ではありません。前章で見たとおり、mblen関数を駆使してカウントする必要があります。
ここでは手抜きをして、32文字の固定長の配列を用意していますが、プログラムの内容次第では、きちんと計算しないといけないかも知れません。あるいは可能性がある上限値が分かっているのなら、その値を使えばよいでしょう。いずれにしても、ヌル文字の分を忘れないようにして下さい。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return 0;
}

実行結果

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

変換結果を受け取る配列の要素数が複雑な指定になっています。 文字数は、マルチバイト文字列でもワイド文字列でも変わらないはずですから、まずはワイド文字列の文字数を調べる必要があります。これは、いつものように sizeof演算子を使って配列の要素数を得ればよいです。
文字数が分かったら、そこに MB_LEN_MAX の値を掛け合わせて、実際に使用する可能性がある最大のバイト数にします。


C99 (通常の文字列リテラルと、ワイド文字列リテラルとの連結)

C99

C99 では、マルチバイトの文字列リテラルと、ワイド文字列リテラルとの連結結果は、両者を合体させたワイド文字列リテラルになることが規定されました(C99以前は未定義でした)。

VisualStudio 2015/2017 では、この規定には対応していません。

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

int main(void)
{
    const wchar_t* const str = "abc" L"あいうえお";

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

    return 0;
}

実行結果:

abcあいうえお


練習問題

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

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

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

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


解答ページはこちら

参考リンク

更新履歴

'2018/5/25 第48章の練習問題④、⑥を移動してきて、練習問題①、②とした。既存の①~②は2つずつずらした。

'2018/4/5 VisualStudio 2013 の対応終了。

'2018/4/2 「VisualC++」という表現を「VisualStudio」に統一。

'2018/3/22 全面的に文章を見直し、修正を行った。
「wchar_t型」の項を「ワイド文字」に統合。
「マルチバイト文字との相互変換」の項を「マルチバイト文字をワイド文字に変換する」「ワイド文字をマルチバイト文字に変換する」「マルチバイト文字列をワイド文字列に変換する」「ワイド文字列をマルチバイト文字列に変換する」に分割した。

'2018/3/2 第27章から「C99 (通常の文字列定数と、ワイド文字列定数との連結)」の項を移動してきた。

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





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

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

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

Programming Place Plus のトップページへ


このエントリーをはてなブックマークに追加
rss1.0 取得ボタン RSS