C++ におけるマルチバイト文字の扱い

文字リテラルと内部表現

C++ では二重引用符で囲まれた文字列は文字列リテラルと解釈される.

const char *str = "Hello"

このコードは次の意味を持つ.
1. 文字列を const char 型の配列としてメモリ上に保存する.
2. const char 型配列から const char* 型への暗黙的な型変換が行われる.(そのポインタは配列の先頭アドレスを指す.)
3. 配列の先頭アドレスを指すポインタで,str が初期化される.

ここで興味があるのが,「Hello」という文字列がメモリ上でどのようなビット列で格納させるのかということである.
これは次のコードで確認できる.

#include <iostream>
#include <iomanip>

char* str = "Hello";
int size = strlen(str);

for (int i = 0; i < size; i++) {
    std::cout << std::hex << static_cast<unsigned>(static_cast<unsigned char>(str[i])) << " ";
}

char型配列の要素の値を1つずつ16進数で表示するものである.
その結果,文字列の内部表現はAsciiコードであることがわかる.

H e l l o \0
48 65 6c 6c 6f 0

このようにAscii文字であれば,char 型配列の1要素が1文字に対応するので,文字列の長さを知りたければ以下のようにすればいい.

// Null文字は含まれない.
std::cout << "length of str: " << strlen(str) << std::endl;
> length of str: 5

次に日本語の場合はどうだろうか.

char* str = "こんにちは";

同様に配列の中身を16進数で確認すると,

\0
82 b1 82 f1 82 c9 82 bf 82 cd 0

今度は,文字列の内部表現はShiftコードであることがわかる.

1文字が char 配列の2要素に対応しているので,先ほどのように strlen() で文字列の長さを取得しようと思っても期待通りの結果にはならない.
なぜなら,strlen はあくまで null文字を除いた char型配列 の長さを返す関数だからである.

// Null文字は含まれない.
std::cout << "length of str: " << strlen(str) << std::endl;
> length of str: 10

ワイド文字

C++にはもう1つワイド文字というものがあった.
文字リテラルの二重引用符の前にLをつけると,これは const w_char 型の配列となる.

const w_char *str = L"こんにちは"

w_char 型 は,型の大きさを増やして日本語のようなマルチバイト文字やAscii文字もすべて1つの型で表現できるようにしたものである.

std::cout << "char: " << sizeof(char) << std::endl;
std::cout << "wchar_t: " << sizeof(wchar_t) << std::endl;
> char: 1
> wchar_t: 2

先ほどと同様に,配列の中身を表示してみる.

wchar_t* str = L"こんにちは";
int size = sizeof(wchar_t) * 5;
unsigned char *p = reinterpret_cast<unsigned char*>(str);
for (int i = 0; i < size; i++) {
    std::cout << std::hex << static_cast<unsigned>(p[i]) << " ";
}

今回は文字列の内部表現はUTF8である

\0
53 30 93 30 6b 30 61 30 6f 30 0

wchar_t 型配列は1文字1要素となっているため,wcslen() で文字数を取得できる.

std::cout << "length of str: " << wcslen(str) << std::endl;
> length of str: 5

注意したいのは,w_char 型で保証されるのはマルチバイト文字も含め1つの型で表現されることだけであり,上で見てきたような w_char 型が2バイトであるとか,内部表現はUTF8であるとかはコンパイラ依存で,環境によっては違ってくる可能性もある.
1文字が1つの型に対応しているというのは,直感的で扱いやすいが,環境依存の部分が多いため,あまり使われていないようである.

文字コードを指定するには

二重引用符で囲んだだけの文字リテラルは,内部表現がどうなるかはコンパイラ依存である.
しかし,それを明示的に指定したい場合も出てくるかもしれない.
C++11には,UTF8リテラル,UTF16リテラル,UTF32リテラルというものがある.
それぞれ二重引用符の前にu8,u,Uをつけるだけでいい.

const char* str1 = u8"こんにちは"; // UTF8でエンコードされる.
const char* str2 = u"こんにちは"; // UTF16でエンコードされる.
const char* str3 = U"こんにちは"; // UTF32でエンコードされる.

まとめ

  • 文字リテラルがどんな文字コードで表わされるかはコンパイラ依存である.
  • マルチバイト文字を std::string や char* 配列に入れて使用する場合は,文字数を取得したりする関数は期待通りの動作はしないので注意する.
  • ワイド文字はコンパイラ依存の部分が多いので,あまり使われない.
  • C++11では,u8,u,U を先頭につけると,エンコード形式を指定できる.

コメントを残す

メールアドレスが公開されることはありません。