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型の大きさは固定的です(環境によって異なりますが、同じ環境下では同じ大きさ)。

C11 (__STDC_MB_MIGHT_NEQ_WC__)

C11 で、__STDC_MB_MIGHT_NEQ_WC__ という事前定義マクロ(第29章)が追加されました。

このマクロは、基本文字集合(第8章)の文字を wchar_t型で表現したとき、その値が文字定数の値と一致しない可能性があるかどうかを表しています。一致しない可能性がある場合にだけ定義され、その置換結果は 1 です。

つまり、以下のコードで if の結果が偽になる可能性があるときに定義されます。

if( L'x' == 'x' ) {}

C99 までは、これが常に保証されていました。

C11 (char16_t、char32_t)

C11 で、ワイド文字を表現する新たな2つの型、char16_tchar32_t が加わりました。いずれも、<uchar.h> という標準ヘッダで定義されています。

char16_t型は 16ビットの符号無し整数型、char32_t型は 32ビットの符号無し整数型です。

char16_t型は uint_least16_t型と同じ、char32_t型は uint_least32_t型と同じと定義されています。

char16_t型は 1文字を 16ビットで表現します。普通は UTF-16 で表現され、その場合に限って、事前定義マクロ __STDC_UTF_16__ が定義されています。このマクロが定義されていない環境では、実装定義のエンコーディング形式が使われます。

char32_t型の方も同様です。1文字を 32ビットで表現し、普通は UTF-32 で表現され、その場合に限って、事前定義マクロ __STDC_UTF_32__ が定義されています。このマクロが定義されていない環境では、実装定義のエンコーディング形式が使われます。

char16_t型のリテラルには uプリフィックスを、char32_t型のリテラルには Uプリフィックスを付加します。

#include <uchar.h>

char16_t c16 = u'あ';
char32_t c32 = U'あ';

const char16_t* s16 = u"あいうえお";
const char32_t* s32 = U"あいうえお";


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

<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関数の使い方はシンプルで、特に迷うことは無いと思います。

C11 (char16_t、char32_t の場合)

str~系、wcs~系の標準ライブラリ関数に対応して、char16_t型や char32_t型を扱うことができる関数はありません。

そのため、どうしても必要であれば、ほかの表現 (char、wchar_t) に変換する必要があります。マルチバイト文字への変換や、マルチバイト文字から変換を行う関数だけは用意されています。

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

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

C11 (マルチバイト文字を char16_t/char32_t に変換する)

マルチバイト文字から char16_t型の文字に変換するには mbrtoc16関数を、char32_t型の文字に変換するには mbrtoc32関数を使います。いずれも、<uchar.h> に以下のように宣言されています。

size_t mbrtoc16(char16_t* pc16, const char* s, size_t n, mbstate_t* state);
size_t mbrtoc32(char32_t* pc32, const char* s, size_t n, mbstate_t* state);

詳細はリファレンスを参照して下さい。

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

ワイド文字をマルチバイト文字へ変換するには、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'多';
    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マクロは、環境が対応している全てのロケールの中で、最も大きな文字の大きさに置換されます。MB_LEN_MAX は、limits.h に定義されています。

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

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

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

C11 (char16_t/char32_t をマルチバイト文字に変換する)

char16_t型の文字からマルチバイト文字に変換するには c16rtomb関数を、char32_t型の文字からマルチバイト文字に変換するには c32rtomb関数を使います。いずれも、<uchar.h> に以下のように宣言されています。

size_t c16rtomb(char* dest, char16_t c16, mbstate_t* ps);
size_t c32rtomb(char* dest, char32_t c32, mbstate_t* ps);

詳細はリファレンスを参照して下さい。

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

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

マルチバイト文字列をワイド文字列へ変換するには、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 <limits.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.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 の値を掛け合わせて、実際に使用する可能性がある最大のバイト数にします。


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

(C95 では) マルチバイトの文字列リテラルと、ワイド文字列リテラルとの連結結果は未定義です。

"abc" L"あいうえお";  /* 未定義 */

C99 からは結果が規定されています。

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

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

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

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

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

    return 0;
}

実行結果:

abcあいうえお

C11 (文字列リテラルの連結)

C11 では、さらに char16_t型と char32_t型が追加されたほか、u8プリフィックス(第46章)も加わっているため、文字列リテラルの連結のパターンが増えています。

プリフィックスを持たない通常の文字列リテラルと、プリフィックスを持つ文字列リテラルは連結することができ、プリフィックスがある方に合わせられます。

const wchar_t str1[]  = "abc" L"あいうえお";  // OK。L の方に合わせる
const char16_t str2[] = "abc" u"あいうえお";  // OK。u の方に合わせる
const char32_t str3[] = "abc" U"あいうえお";  // OK。U の方に合わせる

u8プリフィックスが付加された文字列リテラルと、ほかのプリフィックスが付加された文字列リテラルの連結はできません。

const void* str4 = u8"abc" L"あいうえお";  // エラー

一方、異なるプリフィックスを持つワイド文字列リテラル同士の連結が可能かどうか、また可能であればどのような結果になるのかは処理系定義です。

const void* str5 = u"abc" L"あいうえお";  // 処理系定義

これらの連結は、VisualStudio 2015/2017、clang 5.0.0 のいずれでもコンパイルエラーになります。


練習問題

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

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

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

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


解答ページはこちら

参考リンク



更新履歴

'2018/9/7 「C11 (__STDC_MB_MIGHT_NEQ_WC__)」の項を追加。

'2018/7/5 「C11 (char16_t、char32_t)」「C11 (char16_t、char32_t の場合)」「C11 (マルチバイト文字を char16_t/char32_t に変換する)」「C11 (char16_t/char32_t をマルチバイト文字に変換する)」の項を追加。
文字列リテラルの連結に関する話題を、C95/C99/C11 のそれぞれに分けて記述するように変更。

'2018/7/2 MB_LEN_MAX が定義されているヘッダが間違っていたのを修正 (stdlib.h -> limits.h)

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

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



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

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

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

Programming Place Plus のトップページへ


はてなブックマーク Pocket に保存 Twitter でツイート Twitter をフォロー
Facebook でシェア Google+ で共有 LINE で送る rss1.0 取得ボタン RSS
管理者情報 プライバシーポリシー