結局utf-8がベストだけど
2008.07.26 *Sat
ここのところとある文字列処理ルーチンを書いていて、しかもWindowsとLinuxの両方に対応させなければいけなくて、もともと文字コードに関する知識がないのもあり、すごく苦しんだので、わかったことをここにメモ。知っている人にとっては何を今更の話。
1. wchar_tのフォーマットはプラットフォーム依存。WindowsではUTF-16のリトルエンディアンの方、LinuxではUCS-4-INTERNAL。FreeBSDではそのときのロケールによるらしい。wchar_tは、一文字のバイト数サイズが一定になる(でもプラットフォーム依存…)という利点はあるが、今までの1バイト文字とは互換性がなく、またエンディアンの問題が発生する(深刻)。しかもUTF-16の場合は16ビットですべての文字をあらわそうとするので、表現力が乏しい。
2. utf-8は一文字あたりのバイト数が1バイトから6バイトまで可変なので、utf-8文字列をポンと与えられても、全部で何文字あるのかがわかりづらい。しかし、これまでの1バイトの文字と完全な互換性があるのでstrlenでバッファの長さが取得できたり(文字の数ではない)、また最初から読まなくても文字の切れ目がわかりやすかったり、一文字のバイト数が、先頭バイトの1の数を数えればいいなど、扱いやすい。しかもエンディアンの問題がない。とはいえ、先頭にBOMをつけるか否かというような自由度が残っていることはやるせない。Wikipedia
参考に、utf-8文字列の文字数を求めるプログラムの例(BOMなしを仮定):
3. wchar_t用のC関数としてwprintfやfwprintfなどがあるが、Linuxのgccではワイド文字対応fopenが存在しない。なんだかすごく中途半端です。結局、ワイド文字はやめて、普通のfopenを使うことになるけど、そうするとVC2005にはそんな古い関数を使うなと怒られる。また、wchar_t文字列をwprintfに食わせれば正しく表示できるかと思うと大間違い。Linuxの場合、端末のエンコードによって表示できないことが多々ある。当たり前といえば当たり前なんだけど、じゃあどういうエンコードにすりゃあいいのかはわからずじまい。なぜかというと、端末の右ボタンメニューででてくるエンコードが、SJIS, EUC, JIS, utf-8の4種類しかないから…UCS4はどこですか。普通のワイド文字列を、ワイド文字列用の関数でprintしているのに文字が出ないとはこれいかに。何か勘違いしているんでしょうか。
4. 文字列の変換にはiconvというのを使うことができる。(ベストかどうかは知らないけど)
以下utf8とwchar_tの変換を行うサンプルを載せる。
5.その他情報源 1,2,3
と、いうことで、2バイト以上の文字コードの問題って昔っから言われ続けているけど、今でもあんまし解決していないことに驚愕。歴史的に、新しいコードが提案されるたびに解決どころか混乱が増してるよね。wchar_tで一文字を固定長にするというのはそれができりゃあ素晴らしいけどそれは現実問題として理想論であって、過去の資産もあるし、また文字の数ってそれはもうたくさんあるから、utf-8のような解決が一番実質的によいように思われる。じゃあこれからはutf-8だけで生きていこうとすると、私はwstringのような便利なクラスでutf-8を扱えるやつを知らないので、結局wchar_tと併用することになった。まあとりあえずプログラムは動いたからよかったけど、こりゃあ辛いよなあ。もうやりたくないよなあ。
1. wchar_tのフォーマットはプラットフォーム依存。WindowsではUTF-16のリトルエンディアンの方、LinuxではUCS-4-INTERNAL。FreeBSDではそのときのロケールによるらしい。wchar_tは、一文字のバイト数サイズが一定になる(でもプラットフォーム依存…)という利点はあるが、今までの1バイト文字とは互換性がなく、またエンディアンの問題が発生する(深刻)。しかもUTF-16の場合は16ビットですべての文字をあらわそうとするので、表現力が乏しい。
2. utf-8は一文字あたりのバイト数が1バイトから6バイトまで可変なので、utf-8文字列をポンと与えられても、全部で何文字あるのかがわかりづらい。しかし、これまでの1バイトの文字と完全な互換性があるのでstrlenでバッファの長さが取得できたり(文字の数ではない)、また最初から読まなくても文字の切れ目がわかりやすかったり、一文字のバイト数が、先頭バイトの1の数を数えればいいなど、扱いやすい。しかもエンディアンの問題がない。とはいえ、先頭にBOMをつけるか否かというような自由度が残っていることはやるせない。Wikipedia
参考に、utf-8文字列の文字数を求めるプログラムの例(BOMなしを仮定):
inline unsigned int CountUTF8Characters( const char *_value,int str_len=-1 ){
char* value = (char*)_value ;
unsigned int count = 0;
unsigned int bytes = (str_len<=0?strlen(value):str_len) ;
for(int bi=0;biunsigned char ucb = *(unsigned char*)value ;
++count ;
if( ucb <= 127 ){ ++bi ; ++value ; continue ;}
while( ucb&0x80 ){ ucb<<=1 ; ++bi ; ++value ; }
}
return count ;
}
3. wchar_t用のC関数としてwprintfやfwprintfなどがあるが、Linuxのgccではワイド文字対応fopenが存在しない。なんだかすごく中途半端です。結局、ワイド文字はやめて、普通のfopenを使うことになるけど、そうするとVC2005にはそんな古い関数を使うなと怒られる。また、wchar_t文字列をwprintfに食わせれば正しく表示できるかと思うと大間違い。Linuxの場合、端末のエンコードによって表示できないことが多々ある。当たり前といえば当たり前なんだけど、じゃあどういうエンコードにすりゃあいいのかはわからずじまい。なぜかというと、端末の右ボタンメニューででてくるエンコードが、SJIS, EUC, JIS, utf-8の4種類しかないから…UCS4はどこですか。普通のワイド文字列を、ワイド文字列用の関数でprintしているのに文字が出ないとはこれいかに。何か勘違いしているんでしょうか。
4. 文字列の変換にはiconvというのを使うことができる。(ベストかどうかは知らないけど)
以下utf8とwchar_tの変換を行うサンプルを載せる。
#include <iconv.h>
#ifdef _WIN32
#define WCHAR_T_TYPENAME "UTF-16LE"
#else
#define WCHAR_T_TYPENAME "UCS-4-INTERNAL"
#endif
// general string conversion between UTF-8 and wchar_t by iconv
// the last argument (strlen) should be specified for
// non-null-terminated string.
// (otherwise it's computed as strlen(str)
// the return value is a newly created memory area and therefore
// MUST be deleted afterwards.
char* StrConv(const char* str,bool bUTF8_to_WCHAR_T,int str_len=-1 ){
const char* from = (bUTF8_to_WCHAR_T?"UTF-8":WCHAR_T_TYPENAME) ;
const char* to = (bUTF8_to_WCHAR_T?WCHAR_T_TYPENAME:"UTF-8") ;
iconv_t cd = iconv_open(to,from) ;
if( cd == (iconv_t)-1 ) return 0 ;
size_t ilen, olen ;
// Estimate the output buffer size
ilen = (str_len<=0?strlen(str):str_len) ;
olen = 6*(ilen+1) ; // Large enough for any character of utf-8 (one character can be 6 bytes..maybe too large?)
//printf("ilen=%d,olen=%d\n",ilen,olen) ;
char* retbuf = new char[olen] ;
char* _retbuf = retbuf ;
#ifdef _WIN32
if( iconv(cd, &str, &ilen, &_retbuf, &olen) == -1 ){
#else
char* _str = (char*)str ;
if( iconv(cd, &_str, &ilen, &_retbuf, &olen) == -1 ){
#endif
fprintf(stderr, "iconv error!\n") ;
delete[] retbuf ;
return 0 ;
}
for( int i=0;i<6;++i ) *_retbuf++=0 ;
iconv_close(cd) ;
return retbuf ;
}
5.その他情報源 1,2,3
と、いうことで、2バイト以上の文字コードの問題って昔っから言われ続けているけど、今でもあんまし解決していないことに驚愕。歴史的に、新しいコードが提案されるたびに解決どころか混乱が増してるよね。wchar_tで一文字を固定長にするというのはそれができりゃあ素晴らしいけどそれは現実問題として理想論であって、過去の資産もあるし、また文字の数ってそれはもうたくさんあるから、utf-8のような解決が一番実質的によいように思われる。じゃあこれからはutf-8だけで生きていこうとすると、私はwstringのような便利なクラスでutf-8を扱えるやつを知らないので、結局wchar_tと併用することになった。まあとりあえずプログラムは動いたからよかったけど、こりゃあ辛いよなあ。もうやりたくないよなあ。
CATEGRY : 勉強
COMMENT
wchar_tでも・・・
wchar_tでもUTF-16だったら、サロゲートペアがあるので、厳密には要素数=文字数にはならないはずです。
wchar_tは事実上windows用ですね・・・
wchar_tは事実上windows用ですね・・・
2008/07/26(土) 21:18:56 | URL | shn #- [Edit]
No title
ほんとだ知らなかった。どうもです。
しっかし調べてみたらサロゲートペアなんて最悪じゃないですか。utf16使えなさすぎる...
でも、unixで標準的に使える文字列クラスって何かあります?wstringはそういう意味はありそうですが。utf-8のいい文字列クラスって何かご存知ですかい?
しっかし調べてみたらサロゲートペアなんて最悪じゃないですか。utf16使えなさすぎる...
でも、unixで標準的に使える文字列クラスって何かあります?wstringはそういう意味はありそうですが。utf-8のいい文字列クラスって何かご存知ですかい?
2008/07/26(土) 21:39:19 | URL | owd #- [Edit]
Comment Form
TRACKBACK
TrackBack List
| HOME |