C言語では signed と unsigned の違いで挙動がこんなにも変わる!

通常の業務とは別にC言語を少しずつ勉強しています。C言語の鬼門と言われるポインタの動きもある程度理解し、構造体、線形リストやハッシュなどを使った簡単なプログラムを書けるようになりました。しかし、C言語としてかなり初歩的なことにも関わらず、今まであんまり意識せずにいたために致命的な思い違いをしたままだった事が幾つかあります。今回はその中でもかなり初歩的で、C言語を勉強した人の多くは同じような疑問を抱いたのではないかと思うことについて書きます。

皆さんは知ってましたか?C言語では必ずしも 100+100=200にならないって。

C言語の関連記事:

C言語では必ずしも 100+100 = 200ではない!?

まず初めに100+100=200という想定通りの結果になるプログラム(add1.exe)。
※厳密に言うと、100+100だから問題なく表示されるだけで、大きな整数値を使うと想定外の結果になり得るコードです。

/** add1.exe **/
#include<stdio.h>

int main(){

signed int s = 100;
unsigned int u = 100;

signed int a = s + u;
printf("aの場合 %d+%d = %dn",s,u,a);

unsigned int b = s + u;
printf("bの場合 %d+%d = %dn",s,u,b);

return 0;
}

このプログラムを以下のように実行すると・・・

$ ./add1.exe
aの場合 100+100 = 200
bの場合 100+100 = 200

と表示され、普通に足し算がされています。
しかし、使用する変数をint型ではなくchar型に変更したプログラム(add2.exe)を試すと・・・・

/** add2.exe **/
#include<stdio.h>

int main(){

signed char s = 100;
unsigned char u = 100;

signed char a = s + u;
printf("aの場合 %d+%d = %dn",s,u,a);

unsigned char b = s + u;
printf("bの場合 %d+%d = %dn",s,u,b);

return 0;
}
$ ./add2.exe
aの場合 100+100 = -56
bの場合 100+100 = 200

「おいおい、なんでだよ。100+100=-56 なわけないだろう。なぜ??」
算数が理解できない子供のような気持ちになりました。正の数と正の数を足して負の数になる計算なんて今までの義務教育では習ってません。正直わけがわかりませんでした。

一旦落ち着き、まずは分析することにしました。まず初めにうまく動いたadd1.exeとadd2.exeの違いはint型かchar型の違いです。

/** add1.exeの場合 **/
signed int s = 100;
unsigned int u = 100;

/** add2.exeの場合 **/
signed char s = 100;
unsigned char u = 100;

さらにadd2.exeでもbの場合は想定した答えになっています。

$ ./add2.exe
aの場合 100+100 = -56
bの場合 100+100 = 200

ではadd2.exeでaの場合とbの場合の違いは・・・。

/** add2.exeの場合 **/
signed char a = s + u;
unsigned char b = s + u;

signed char か unsigned char です。 ということはsignedかunsignedかによって答えの表示が変わってくるということになります。

signed char が扱える数の範囲は-128~127。なるほど、つまり 100+100=200 だけど、signed char として200という表示は行うことが出来ず、-56になっていることになります。

なぜ正の数 200 と負の数 -56 は一緒なの?

では次の疑問です。なぜ200は-56になるかです。

この疑問を解消するためにまず signed と unsigned の char にそれぞれ100を代入し、printfで表示した後にインクリメントする処理を100回繰り返す次のようなプログラム(increment.exe)で実験を行いました。

#include<stdio.h>

int main(){

	signed char s = 100;
	unsigned char u = 100;

	int i;
	for(i=100;i <= 200 ;i++){
		printf("%d %03dn",s,u);
		u++;
		c++;
	}

	return 0;
}

その結果がこちらです。

$ ./increment.exe
100 100
101 101
102 102
 (省略)
126 126
127 127
-128 128
-127 129
 (省略)
-57 199
-56 200

signed char が扱える数の範囲は-128~127と書きましたが、インクリメントをしていって128になったときの表示は-128になるんですね。その後、ずっと200まで1足していき、結果として-56になったわけです。

200 と -56 、ビットパターンはどうなるの?

さらに疑問です。2つの違いは「signed」か「unsigned」ということだけなので、表示上では異なる表示を行っていますがビットパターンとしてはどうなるのでしょうか。

Windows標準搭載の関数電卓で200を2進で表示してみました。

2shin.png

ビットパターンが「11001000」ということはsigned(符号付)では以下のように計算でき、確かに-56になります。

//ビットパターンで10000000は-128
//ビットパターンで01001000は72

-128 + 72 = -56

これであれば確かにadd2.exeのsigned char に計算結果を代入した答えが-56になるわけです。

C言語ではsigned(符号付)かunsigned(符号なし)かによって表示や計算の挙動が変わることを念頭に置かないと、同じピットパターンでも結果が大きく異なってしまいます。当たり前と言えば、当たり前ですが良い勉強になりました。

  • sherpanman
    オーバフローですね