はじめに
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
) 定数として定義したほうが余計な落とし穴に落ちにくいかと思います。