はじめに
C プラグラマならわかっていることとは思いますし、いまさらな内容ではありますが、たまたま静的解析ツールで指摘された内容が面白かったので一般化して書いてみます。
まずはサンプルコード
# include <stdio.h>
# define VALUE 255
int main(void) {
char c = VALUE;
if (c == VALUE) {
puts("TRUE");
} else {
puts("FALSE");
}
return 0;
}
さてこのプログラムを実行すると何が出力されるでしょうか。
手元の環境 (Ubuntu 16.04LTS, gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609) では以下のようになりました。
FALSE
全く直感的ではないですね。
char は符号つきか符号なしかは処理系定義
char が符号つきか符号なしかは処理系によって変わります。
(int や long が常に符号つきなのとは違います。わかりにくい…)
典型的には
- 符号つき: [-128, 127]
- 符号なし: [0, 255]
の値を取ることになります。
上記の環境では「符号つき」でした。よって、 255 は表現できません。
char c = VALUE;
これによって c の値は (8 bit かつ2の補数表現の場合) -1 になります。
if (c == VALUE) {
ここが問題です。左辺は char, 右辺は int です。
C の整数拡張のルールにより、c は int に変換されます。すなわち int の -1 です。
右辺のはそのままで int の 255。
よってこの条件式は偽となります。
これだけ見たら難しい話ではないのですが、この VALUE を定義しているところと実際に使っているところが離れていたりするとかなり追いにくい問題になるでしょう。
ちなみに
gcc だと -fsigned-char / -funsigned-char という オプション があり、挙動を切り替えることができます。
上記プログラムも -funsigned-char をつけてコンパイルすると TRUE の結果が得られます。
どうするのがよいの ?
文字や文字列ではなく、数値を 8bit で扱いたいときは int8_t / uint8_t、少なくとも sigined char / unsigned char を使い、 char は使わないほうがよいでしょう。
また、特に C++ であれば定数はプリプロセッサマクロではなく型つきの const (constexpr) 定数として定義したほうが余計な落とし穴に落ちにくいかと思います。