LoginSignup
17
9

More than 5 years have passed since last update.

wchar_tは何ビット?ワイド文字の取扱いメモ

Posted at

突然ですが

C言語のwchar.hで定義されているwchar_tって何ビットでしょう?

16ビットだと思いましたか?(私も思いました)
実は環境依存です。16ビットじゃない時もあるのです。

※話を簡単にするため、ここではサロゲートペアは考えません。

検証

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

int main() {
  wchar_t *s = L"ABCD";
  printf("%d %d\n", wcslen(s), sizeof(s[0]));
  return 0;
}

これをgccでコンパイルしたとき、結果は

  • Windows & gcc 6.4.0: 4 2
  • Linux & gcc 4.8.5: 4 4

となりました。
ワイド文字を表すL""と、文字数を数えるwcslen()とは辻褄が合うように作られていますが、wchar_tの実態は前者が16ビット、後者が32ビットと異なる結果に。

何が困るか

外部から与えられたUnicode文字列データが、1文字16ビット(UTF-16)のヌル終端文字列として表現されていたとしましょう。
その文字列と文字数を出力しようとすると、例えば以下のようなプログラムになりそうです。

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

int main() {
  char data[] = {0x40, 0x00, 0x41, 0x00, 0x42, 0x00, 0x00, 0x00}; // これが与えられたとする
  wchar_t *s = (wchar_t *)data;
  printf("%ls %d\n", s, wcslen(s));
  return 0;
}

ただし、これはwchar_tが16ビットの環境では

@AB 3

と期待通りに表示されますが、32ビットの環境だと予期しない動きをします。

どうすればよいか

C++11で、UTF-16の文字1個(サロゲートペア除く)を表すデータ型としてchar16_tという型ができました。同様に、32ビットの文字はchar32_tとなります。
また、UTF-16/UTF-32で文字列リテラルを表す記法も追加されています。

冒頭の例は、以下のように書けば良さそうです(C++ですが)。

main.cpp
#include <stdio.h>
#include <string>

using namespace std;

int main() {
  char16_t s[] = u"ABCD"; // UTF-16文字列リテラル
  printf("%d %d\n", char_traits<char16_t>::length(s), sizeof(s[0]));
  return 0;
}

コンパイルオプションでC++11を明示してください。

terminal
g++ -std=c++11 main.cpp

wchar_tのサイズに関係なく、4 2 が出力されます。

wchar_tと異なり、char16_tprintf()関数では出力手段がありません。
代わりに、こんな感じでいけそうです。(ソースコードはUTF-8で保存してください)

#include <string>
#include <codecvt>
#include <locale>
#include <iostream>

using namespace std;

int main() {
  char16_t s[] = u"あいうえお";
  wstring_convert<codecvt_utf8<char16_t>, char16_t> cv;
  cout << cv.to_bytes(s) << endl;
  return 0;
}

ただしcodecvt_utf8の利用には以下の条件が付きます。→codecvt_utf8 - cpprefjp - C++日本語リファレンス

  • C++17では非推奨
  • GCC 5.1以上が必要

wchar_tトラップ:Java (JNI) の場合

Javaで1文字を表すchar型は16ビットです。
例えば、JNI(Java Native Interface)において、UTF-16(ヌル終端)で表された文字列データをJavaのString型(jstring)として返したいと思ったときに、文字数をwcslenで数えてしまうとハマります。

C++のコード
// jbyteArray (byte[]) 型の引数argが与えられたとする
jbyte *arg_ptr = env->GetByteArrayElements(arg, NULL);
// wcslenが予期しない結果になるかも
jstring ret_string = env->NewString((jchar *)arg_ptr, wcslen((wchar_t *)arg_ptr));
env->ReleaseByteArrayElements(arg, arg_ptr, 0);

対策としては、こんな感じでしょうか?(必要なヘッダの宣言とusing namespace std;を書いておいてください)

jstring ret_string = env->NewString((jchar *)arg_ptr, char_traits<char16_t>::length((char16_t *)arg_ptr));

ヌル終端文字列の長さを調べるだけなら、自分でforループ回してもできますが…

wchar_tトラップ:Python (ctypes) の場合

PythonからC/C++の共有ライブラリ (.dll, .so) を呼び出すためのctypesというライブラリがあります。
ここでも、バイト列からUnicode文字の配列を作って操作したり、他の関数に渡したりしようとしてハマる可能性があります。

Python
import ctypes
wstr = ctypes.create_unicode_buffer(u"あいうえお")
print(ctypes.sizeof(wstr))

wchar_tが16ビットの環境だと12が、32ビットの環境だと24が出力されます。
ここでいう「環境」とは、Pythonのビルドに使われたコンパイラの環境のようです。

実は、create_unicode_buffer()自体があまり使い所のないものなのかもしれません。
WindowsのAPIを扱うときには、wchar_t *型の引数などを相手にするので良いのでしょうが。

まとめ

wchar_t怖い。wcslen怖い。
私のように、なめてかかってひどい目に遭う人が増えないように祈っています。

17
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
9