Version 11.05
signed char と unsigned char
「今回は signed と unsigned について説明します」
『それ自体は前にやったよね。符号があるのとないのって』
「 Version 4.06 ( No.056 ) で説明したように、 signed が符号あり、
unsigned が符号なしです」
『でもさ、それってあんま関係ないと思うんだけど。だって文字でしょ?』
「それがおおありなんです。まず、その singed とかを説明したページにあ
るコードをもう一度試してみます」
void Compare()
{
char ch = -1;
unsigned char uch = ch;
if( ch < uch )
{
TRACE( "%d < %u\n", ch, uch );
}
}
// -1 < 255
「何も付けてないと signed と同じだから、 ch の方は signed です。で、
それと unsigned を比較すると」
『そうそう、同じ -1 でも、 unsigned だと 255 になっちゃうんだよね』
「あ、でも勘違いしないで欲しいんだけど、ビット的には ch も uch も同
じ 0xFF だからね」
『え、そうなの?』
unsigned char uch = ch;
「ってした時には、実際には何も起きてません。こっちじゃなくて」
if( ch < uch )
「このとき、 ch は 0xFF を -1 、 uch は 0xFF を 255 ってみなして」
『それで比較しちゃうわけね』
「言い換えると、これは < の機能ってことかも」
『機能??』
「そう。 < は signed か unsigned か調べて、それを元に整数値に変換し
て比較してるってこと」
『そういう機能が備わってる……?』
「この辺はちょっと難しいかもね。演算子ってただ計算をするだけじゃなく
て、いろんな機能もあるんです。そういうののひとつってところかな」
『うーん……』
「まぁそれは今は置いておいて、重要なのは signed か unsigned かで比較
したときの結果が変わっちゃうってこと」
『それって重要なの?』
「すごく重要。だって前回、 Shift JIS のリードバイトの範囲とトレイル
バイトの範囲って言ったでしょ」
『あーっ!!! そっか、その範囲に入ってるかどうかって、もちろん比較
しなきゃいけないわけだから……』
「では実際に試してみます。リードバイトかどうか判別する関数の signed
を使うほうから」
bool IsReadByte_bad( signed char p_ch )
{
if(
( ( 0x81 <= p_ch ) && ( p_ch <= 0x9F ) ) ||
( ( 0xE0 <= p_ch ) && ( p_ch <= 0xFC ) )
)
{
// リードバイトです。
return true;
}
return false;
}
「これを次のような関数から呼びます」
void Use_IsReadByte_bad()
{
const signed char pchStr[] = "あ";
bool bResult = IsReadByte_bad( pchStr[0] );
if( bResult == true )
{
TRACE( "リードバイトです。" );
}
else
{
TRACE( "トレイルバイトです。" );
}
// トレイルバイトです。
}
「 "あ" のリードバイトを渡してるんだから、絶対にリードバイトになるは
ずなんだけど」
『げ、実行したらトレイルバイトって言われた』
「 "あ" の文字コードは 0x82 0xA0 」
『範囲には入ってるよねぇ』
「でも」
0x81 <= p_pch
「この p_pch には 0x82 が入ってるわけだから、普通に比較すると」
0x81 <= 0x82
『だからあってるはずよね』
「ところが、 p_pch は signed だから符号あり。そうなると、互いに符号
ありの比較になります。その場合」
-127 <= -126
「となるため、当てはまらなくなってしまうんです」
『……質問!』
「はい火美ちゃん」
『 0x81 が -127 とかってどうやればわかる?』
「それはこうしてください」
TRACE( "%d\n", 0xFFFFFF81 );
// -127
『う、なんか F が多い』
「こうすると -127 が出てきます」
『なんで F 入れなきゃいけないの?』
「う”……それはかなーり難しいから今度」
『う”ー……あ、もひとつ質問』
「はい火美ちゃん」
『 signed で比較すると逆になっちゃったけど、それって16進数だと大き
いのに10進数だと小さいってことだよね。なんか変くない?』
「さっきのページで教えたでしょ。 -1 は 11111111 、つまり 0xFF 」
『あ、そっか、逆になるんだ』
「だから、実際にはこんな感じ」
16進数 : 0x00 - 0x7F - 0x80 - 0xFF
signed : 0 127 -128 -1
unsigned : 0 127 128 255
「 signed だと、 0x7F と 0xF8 で逆になるって考えてください」
『むー、ややこしい……』
「まぁそういうわけで、この 0x7F 以上の数を使う文字の場合、 signed で
計算するとうまくいきません。なので、 unsigned を使います」
bool IsReadByte( unsigned char p_ch )
{
if(
( ( 0x81u <= p_ch ) && ( p_ch <= 0x9Fu ) ) ||
( ( 0xE0u <= p_ch ) && ( p_ch <= 0xFCu ) )
)
{
// リードバイトです。
return true;
}
return false;
}
// 使います。
void Use_IsReadByte()
{
const unsigned char pchStr[] = "あ";
bool bResult = IsReadByte( pchStr[0] );
if( bResult == true )
{
TRACE( "リードバイトです。" );
}
else
{
TRACE( "トレイルバイトです。" );
}
// リードバイトです。
}
『うん、こっちならちゃんとリードバイトって出た』
「あと、今回は省略するけど、どっちの文字の方が大きいか、っていうのを
比較するときにも関係するから」
『文字の大きさ?』
「たとえば」
ABC
BBC
「ってあった場合、辞書に並べるときはこの順番でしょ」
『そういう大きい小さい?』
「そう。こういう時 A < B みたいな比較をするわけだけど」
『これも unsigned じゃないとうまくいかないわけね』
「さて、ここまでを踏まえて、これからすごくややこしい話をします」
『う”っ、な、なに?』
「今見たように、 char は unsigned char とした方が安全です」
「 unsigned にすると出るデメリットはないの?」
『ありません』
「ならそうした方がいいわけ……よね」
『なんだけど、そうしません』
「そこがややこしいとこなんだ。なんで?」
「うーん、一番の理由は慣例的にそう、だからかな。 unsigned を付けない
char しか使わない、っていうのが普通だし、問題になるのは文字の比較と
か四則演算するときくらいだから、その時キャストすればいいし」
『……二番目の理由は?』
「実は、 char が signed とは決まってないんです」
『へ?』
「 char が実際には signed か unsigned かは開発環境によって違うんで
す。 Visual C++ は char は signed 。 unsigned なのもあるし、最新の
VC .Net は好きな方を選べるしね」
『ならやっぱり、常に unsigned にするべきなんじゃない?』
「確かにそうなんだけどね……これがきっと最大の理由なんだけど、
unsigned char から char へはキャストが必要なんだけど、ランタイムとか
API とかは」
『げ。そっか、普通の char だから、渡すたびにキャストしなきゃいけない
んだ……』
「それはあまりにも大変だし、そうなると結局」
『何も付けない char 使うしかないってわけね……』
/*
Preview Next Story!
*/
『なんだか、政治的策略を感じるわ……』
「そういう部分もあるかも」
『否定しないのー!?』
「こういうことは派閥争いと無縁じゃないから」
『そんでもって掲示板で罵り合うことになるんだ……ぶるぶる』
「というわけで次回」
< Version 11.06 Unicode >
『につづく!』
「今度もまたそういうのだ……」
『また!?』