LoginSignup
22
7

More than 5 years have passed since last update.

C/C++ の char が取る値の範囲に注意

Last updated at Posted at 2019-02-13

はじめに

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 が符号つきか符号なしかは処理系によって変わります。
(intlong が常に符号つきなのとは違います。わかりにくい…)

典型的には

  • 符号つき: [-128, 127]
  • 符号なし: [0, 255]

の値を取ることになります。

上記の環境では「符号つき」でした。よって、 255 は表現できません。


char c = VALUE;

これによって c の値は (8 bit かつ2の補数表現の場合) -1 になります。


  if (c == VALUE) {

ここが問題です。左辺は char, 右辺は int です。
C の整数拡張のルールにより、cint に変換されます。すなわち int-1 です。
右辺のはそのままで int255
よってこの条件式は偽となります。

これだけ見たら難しい話ではないのですが、この VALUE を定義しているところと実際に使っているところが離れていたりするとかなり追いにくい問題になるでしょう。

ちなみに

gcc だと -fsigned-char / -funsigned-char という オプション があり、挙動を切り替えることができます。

上記プログラムも -funsigned-char をつけてコンパイルすると TRUE の結果が得られます。

どうするのがよいの ?

文字や文字列ではなく、数値を 8bit で扱いたいときは int8_t / uint8_t、少なくとも sigined char / unsigned char を使い、 char は使わないほうがよいでしょう。
また、特に C++ であれば定数はプリプロセッサマクロではなく型つきの const (constexpr) 定数として定義したほうが余計な落とし穴に落ちにくいかと思います。

22
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
7