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

# 突然ですが

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

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

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

# 検証

```c: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）のヌル終端文字列として表現されていたとしましょう。
その文字列と文字数を出力しようとすると、例えば以下のようなプログラムになりそうです。

```c
#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++ですが）。

```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を明示してください。

```bash:terminal
g++ -std=c++11 main.cpp
```

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

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

```c++
#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++日本語リファレンス](https://cpprefjp.github.io/reference/codecvt/codecvt_utf8.html)

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

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

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

```c++: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;``を書いておいてください）

```c++
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: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``怖い。
私のように、なめてかかってひどい目に遭う人が増えないように祈っています。