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

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

ワイド文字

前章では、C言語において、ASCIIコードの範疇で表現できない文字(例えば、日本語)を扱うための方法として、 マルチバイト文字を取り上げました。 この章では、もう1つの概念として、ワイド文字を説明します。

1文字を表現するために必要とするバイト数が混在しているマルチバイト文字に対し、 ワイド文字の場合は、必ず決まったバイト数で文字を表現します。 ただし、1文字が 1Byte であるとは限らず、2Byte であったり、4Byte であったりします。 マルチバイト文字との違いは、固定的なサイズなのか、色々なサイズが混在しているのかという点です。

1文字の大きさが固定的であるということは、マルチバイト文字よりも明らかに分かりやすそうです。 実際、C言語の規格は、どちらかというとワイド文字の方のサポートが強力だと言えます。

ワイド文字がどんな文字コードで表現されるかは、マルチバイト文字のときと同様に環境依存となります。 ここでは、Windows環境で標準的な、UTF-16 を想定して話を進めます。

UTF-16 は、1文字を 2Byte単位で表現します。 ただし、一部の文字は 2Byte のコードを 2つペアして表現するため、4Byte の文字も存在することになります。

wchar_t型

ワイド文字を扱う際には、char型ではなく、wchar_t型(⇒リファレンス)という専用の型を使います。
wchar_t型は、int型や char型などのように、C言語の予約語として用意されているものではなく、typedef によって作られた型で、 stddef.h、stdlib.h、wchar.h といった各標準ヘッダで定義されています。

C++ では、wchar_t型は予約語として定義されています。

具体的にどんな風に定義されているかは、環境によって異なりますが、 その環境で使用できるすべての文字を表現するのに十分なサイズを持っています。 例えば、ワイド文字を 2Byte で表現する環境ならば、

typedef short int wchar_t;

のように定義されているでしょう(short型が 2Byte ならば)。

ということなので、wchar_t型をマルチバイト文字を扱うために使うのは間違っていますし、 wchar_t型がどんな環境でも 2Byte であると想定することも間違っています

例えば、sizeof(wchar_t) の結果は、VisualC++ 2013/2015/2017 では 2 になりますが、clang 3.7 では 4 になります。

ワイド文字版の標準関数

C89規格の時点では、ワイド文字を対象とした標準関数はほとんど存在しません。 マルチバイト文字との相互変換を行う mbtowc関数(⇒リファレンス)、 wctomb関数(⇒リファレンス)と、 文字列版の mbstowcs関数(⇒リファレンス)、 wcstombs関数(⇒リファレンス)だけしかありません。 この辺りの相互変換関数は、この後の項で取り上げます

C95規格になると、string.h に定義されている各種の文字列操作関数のワイド文字列版が用意されるようになりました。 これらの関数は、関数名の "str" の部分を "wcs" に置き換えた名前で統一されています。 例えば、strcpy関数(⇒リファレンス)に対応するワイド文字列版は wcscpy関数(⇒リファレンス)、 strcmp関数(⇒リファレンス)ならば wcscmp関数(⇒リファレンス)という具合です。
これらのワイド文字列版は、wchar.h に定義されています。

少し試してみましょう。

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

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


	setlocale( LC_CTYPE, "" );


	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

前章でマルチバイト文字を扱ったときと同様、 setlocale関数(⇒リファレンス)の呼び出しは必要です。

ワイド文字定数や、ワイド文字リテラルを扱う際には、L"" のように、L というプリフィックスを付ける必要があります。 これによって、その文字や文字列がワイド文字であるとみなされます。
なお、文字列の末尾に付くヌル文字は、ワイド文字の場合には L'\0' と表現します。 同様に、改行文字ならば L'\n' と表現します

wcslen関数は、ワイド文字列の文字数を返します。 文字数であることが重要で、バイト数とは意味が異なります( 1文字が 1Byte というワイド文字もあり得ますから、文字数とバイト数が一致する可能性が無い訳ではありません)
一方、sizeof演算子はいつものように、バイト単位の大きさを取得します。

ワイド文字列の出力には、wprintf関数(⇒リファレンス)を使っています。 これは言うまでもなく、printf関数(⇒リファレンス)のワイド文字列版です。
wprintf関数を使ってワイド文字列を出力する際には、%sフォーマットに l を挟みこんで L"%ls" と書きます。 単に L"%s" と書いた場合には、渡された文字列がマルチバイト文字列であるとみなされ、それを自動的にワイド文字列へ変換して出力しようとします。 最初からワイド文字列を渡すのであれば、L"%ls" を使わないといけません。

このサンプルプログラムのように、wchar_t型のポインタを使ってワイド文字列を表現することも、wchar_t型の配列を使って表現することもできます。 これは、通常の(ワイド文字やマルチバイト文字でない)文字列と同様です。

マルチバイト文字との相互変換

マルチバイト文字とワイド文字を、相互に変換できる標準関数が用意されています。 ここでは、C89規格の段階で使える4つの関数について紹介しておきます。

まず、マルチバイト文字をワイド文字へ変換するには、 mbtowc関数(⇒リファレンス)を使います。

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

第2引数に指定したマルチバイト文字を、ワイド文字に変換し、第1引数に指定したアドレスへ格納します。 第3引数には、マルチバイト文字の最大サイズを指定します。 戻り値は、マルチバイト文字の実際のサイズが返されます。エラー時には -1 が返されます。

第1引数を NULL にすることもできますが、その場合は関数の意味合い自体まで変わってしまいます。 少し謎めいた特殊な仕様なので、これについては触れません。

一方、ワイド文字をマルチバイト文字へ変換するには、wctomb関数(⇒リファレンス)を使います。

int wctomb(char* s, wchar_t wc);

第2引数に指定したワイド文字を、マルチバイト文字に変換し、第1引数に指定したアドレスへ格納します。 戻り値は、変換されたマルチバイト文字の大きさです。
結果はマルチバイト文字なので、その大きさは変動します。 前章で説明したように、 MB_CUR_MAXマクロ(⇒リファレンス)が、 現在のロケールでのマルチバイト文字の最大サイズを表すので、 このサイズ以上の配列を用意し、そのアドレスを第1引数に渡さなければなりません。

この関数も mbtowc関数と同様、第1引数を NULL にできますが、こちらも触れないことにします。

まずはこの2つの関数を試してみます。

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

int main(void)
{
	char mb[MB_LEN_MAX + 1];  /* 全ロケールで可能性のあるサイズ + 末尾の '\0' */
	wchar_t wc = L'あ';
	int len;


	setlocale( LC_CTYPE, "" );


	/* ワイド文字からマルチバイト文字へ */
	len = wctomb( mb, wc );
	mb[len] = '\0';
	printf( "%s\n", mb );


	/* マルチ文字からワイド文字へ */
	mbtowc( &wc, mb, MB_CUR_MAX );
	wprintf( L"%lc\n", wc );

	return 0;
}

実行結果

あ
あ

最初に用意したワイド文字 L'あ' を、マルチバイト文字へ変換し、更にそれをワイド文字に変換し直しています。

マルチバイト文字に変換されるとき、変換後のサイズが分からないので、 ここでは MB_LEN_MAXマクロ(⇒リファレンス)を使って配列を用意しています。
MB_CUR_MAXマクロが、現在のロケールにおけるマルチバイト文字の最大サイズに置換されるのに対し、 MB_LEN_MAXマクロは、環境が対応している全てのロケールの中で、マルチバイト文字のサイズが最も大きくなる場合の最大サイズに置換されます。 変数宣言の時点ではまだ、setlocale関数を呼び出していないため、ここでは MB_LEN_MAXマクロを使うようにしています。


次に、これらの変換関数の文字列版です。

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

いずれも、第2引数が変換元の文字列、第1引数が変換後の文字列を受け取るアドレスです。 第3引数は、変換後の文字列を何文字まで作るかを指定します。 つまり、第3引数で指定した数値以上の要素数を持った配列のアドレスを、第1引数に指定しなければなりません。
戻り値は、実際に第1引数の配列に格納された文字数です。 エラー発生時には -1(を size_t型にキャストしたもの)が返されます。

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

#define STR_LEN  5   /* 文字数 */

int main(void)
{
	char mbs[STR_LEN * MB_LEN_MAX + 1];  /* 全ロケールで可能性のあるサイズ + 末尾の '\0' */
	wchar_t wcs[STR_LEN + 1] = L"あいうえお";


	setlocale( LC_CTYPE, "" );


	/* ワイド文字列からマルチバイト文字列へ */
	wcstombs( mbs, wcs, STR_LEN * MB_CUR_MAX + 1 );
	printf( "%s\n", mbs );


	/* マルチ文字列からワイド文字列へ */
	mbstowcs( wcs, mbs, STR_LEN + 1 );
	wprintf( L"%ls\n", wcs );

	return 0;
}

実行結果

あいうえお
あいうえお

基本的にしていることは、1文字版と同じです。

今までもそうですが、文字数とバイト数の違いをしっかり意識しましょう。
例えば、最初に用意しておくマルチバイト文字列用の配列の要素数は、 扱う文字数に MB_LEN_MAXマクロの値を掛け合わせて、'\0' の分を加えた大きさになります。 この配列は char型ですから、要素数とバイト数が一致します。 しかし文字数という観点では、STR_LENマクロが表す 5文字('\0' を合わせれば 6文字)ということになります。


練習問題

問題@ 2つのワイド文字列を用意し、互いの内容が一致しているかどうかを調べるプログラムを作成して下さい。
strcmp関数(⇒リファレンス)のワイド文字列版を使うものと、 使わずに自力で行うものを両方作成して下さい。

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


解答ページはこちら

参考リンク

更新履歴

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/18 VisualC++ 2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2014/10/18 clang 3.2 に対応。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/25 setlocale関数の LC_CTYPE の指定を修正。

'2010/9/9 新規作成。



前の章へ

次の章へ

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

Programming Place Plus のトップページへ