glibc の wcwidth() の「曖昧な文字幅」についての動作

glibcwcwidth() の動作を自分の手できちんと検証したことがなかったので実験してみた。対象バージョンは Debian lenny に含まれていた 2.7-18。
実験に使ったのは以下のプログラム。

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

void print_wcwidth(wchar_t c)
{
  printf("wcwidth('%lc') == %d\n", c, wcwidth(c));
}

int main()
{
  setlocale(LC_CTYPE, "");
  print_wcwidth(0x41);
  print_wcwidth(0x3b1);
  print_wcwidth(0x3042);
  return 0;
}

これをコンパイルして、UTF-8 なターミナルで実行してみると、以下のようになった。

 $ LC_CTYPE=ja_JP.UTF-8 ./a.out
wcwidth('A') == 1
wcwidth('α') == 1
wcwidth('あ') == 2

 $ LC_CTYPE=en_US.UTF-8 ./a.out
wcwidth('A') == 1
wcwidth('α') == 1
wcwidth('あ') == 2

 $ LC_CTYPE=ja_JP.EUC-JP ./a.out | nkf -Ew
wcwidth('A') == 1
wcwidth('α') == 2
wcwidth('あ') == 2

ということで、やはり glibcwcwidth() だと ja_JP.UTF-8 の時に哀しいことが起こることが分かった。
次に、UTF-8 charmap (/usr/share/i18n/charmaps/UTF-8.gz) の WIDTH...END WIDTH までを書き換えて ambiguous width な文字の幅を 2 に変更した後、locale-gen コマンドでロケールデータを更新すると、以下のようになった。

 $ LC_CTYPE=ja_JP.UTF-8 ./a.out
wcwidth('A') == 1
wcwidth('α') == 2
wcwidth('あ') == 2

 $ LC_CTYPE=en_US.UTF-8 ./a.out
wcwidth('A') == 1
wcwidth('α') == 2
wcwidth('あ') == 2

ということで、期待どおり CJK な人にはありがたい結果になることが分かった。代わりに en_US.UTF-8 で哀しいことが起こっているが、CJK な人だけが使っているホストなら charmap を書き換えるというのもアリかも。
なお、実際に書き換えて運用するなら dpkg-divert で本来のファイルを退避すべきかな。

 $ sudo dpkg-divert --rename --add /usr/share/i18n/charmaps/UTF-8.gz

(後日追記) glibc の wcwidth の話の続き を書いた。