はじめに
C言語を学んでいて、足し算の結果が想定と異なることがありました。
この原因について考えます。
100 -10 + 120
3つの数の合計は-46です。
実行環境:Microsoft Visual Studio Community 2019 Version 16.11.19
問題:実行結果が負の値になること
以下のようなシンプルなプログラムを考えます。
#include <stdio.h>
int main(void) {
char sum = 0;
char num = 0;
num = 100;
sum += num;
num = -10;
sum += num;
num = 120;
sum +=num;
printf("3つの数の合計は%dです。\n", sum);
return 0;
}
一見、
100 -10 + 120 = 210
になるはずと思い、
実行してみると、
3つの数の合計は-46です。
と表示される。
原因の予想:char型の表現範囲を超えている
char 型は 8ビット(1バイト) で構成。
つまり、表現できる範囲は次の通り:
| 型 | 範囲 | ビット数 |
|---|---|---|
char |
-128 〜 +127 | 8ビット |
int |
約 -2,000,000,000 〜 +2,000,000,000 | 32ビット |
210という値は、charの上限(127)を超えている。
その結果、オーバーフローが発生すると考える。
2の補数表現とは?
コンピュータは、負の数を表現するために2の補数(two'scomplement) を使います。
8ビットを例に考えてみましょう。
1. 正の数(例:5)
0000 0101
2. 負の数(例:-5)の作り方
- 絶対値(5)のビットを反転
→1111 1010 - そこに1を足す
→1111 1011
これが -5 の2進数表現です。
対応策:sumを適切な型に変更する
では、これを修正してみます。
char → int に変更するだけです。
#include <stdio.h>
int main(void) {
int sum = 0; // ← ここだけintに変更
char num = 0;
num = 100;
sum += num;
num = -10;
sum += num;
num = 120;
sum += num;
printf("3つの数の合計は%dです。\n", sum);
return 0;
}
実行結果
3つの数の合計は210です。
期待通りの結果になりました!
まとめ
| ポイント | 内容 |
|---|---|
| char型の範囲 | -128〜127(8ビット) |
| オーバーフロー | 上限を超えると負の数として扱われる |
| 対策 |
int など広い範囲の型に変更する |
| 理解のポイント | 2の補数表現を知ること |
【追記】
-
charの符号付き/符号なしは処理系依存
→ 一般的にはx86系では符号付き、ARM系などでは符号なしがデフォルト。 -
オーバーフロー時の挙動は未定義または実装依存
→ いわゆる「ラップアラウンド」が起きるかどうかも保証されない。 -
2の補数表現は言語仕様上、必ずしも保証されているわけではない。
☆ コラム☆ プリミティブ型と参照型のちがい
-
C言語の プリミティブ型(値型)
変数そのものが値を直接保持。
たとえばint a = 10;は、メモリ上に「10」という値がそのまま格納されます。 -
ポインタ型
「値が格納されている場所(アドレス)」を保持。
C言語では、これを ポインタ(pointer) で実現します。
つまり、int *p = &a;のように、pは「aのアドレス」を参照。
さいごに
C言語では、「型を正しく選ぶ」ことが重要だと感じた。
もし出力が「おかしい」と感じたら、
まずはデータ型の範囲を確認してみることにします。
実務では、より汎用的な型を選択している理由がよく分かった。