第4回
算術演算と変数~演算子と変数の型

変数と型を考える

様々なデータを保存できる変数は、プログラミングにとって非常に重要な機能です。算術演算子を使わないプログラムはあっても、変数を使わないプログラムはまず見あたりません。変数と型について考えてみましょう。

柔軟性と変数の働き

算術演算には変数が付き物です。

100 + 50
のような計算式は、わざわざプログラミングする必要がありません。変数xにいくら入っているか分からないから

x + 50
のように、様々な局面に応用できる普遍的な式を記述できるのです。プログラムの柔軟性は、変数によって支えられていると言えます。

型の制約

変数がデータの入れ物――ということは、みなさんご存じでしょう。Cに限らず、プログラミングの入門書では大抵そのような説明がなされています。もちろん間違いではないのですが、これだけでは変数の性質を表現できていません。

変数には、プログラムが実行されるまでどのような値が保存されるか分かりません。また、プログラムの実行中にその値が変わる場合もあります。そのような不定な値を名前で抽象化できることによって、プログラムは柔軟に動作できる訳です。

しかし、この便利な変数には『型』という制約があります。表4にCで使える型を掲げておきます。

人間が頭の中で計算する場合、例えば「10÷2」なら答は5でありすべて整数で済みますが、「10÷4」なら「2、余:2」という整数の他に「2.5」という小数の解もあります。さらに「10÷3」なら、「3、余1」と「3.3333...」という無理数、「約3.33」といった形で丸めることも可能になります。

ところがコンピュータでは、変数はメモリ上の一定の範囲に名前を付けたものであり、その範囲はあらかじめ決めておかなければなりません。

表4:Cのデータ型
型名 占有サイズ
(ビット)
範囲 名称
unsigned char 8 0~255 符号なし8ビット整数
signed char 8 -128~+127 符号付き8ビット整数
unsigned short int 16 0~65535 符号なし整数
signed short int 16 -32768~+32767 符号付き整数
unsigned long int 32 0~4294967295 符号なし長整数
signed long int 32 -2147483648~+2147483647 符号付き長整数
float 32 3.4e±38※t1(有効7桁) 浮動小数点数(単精度実数型)
double 64 1.7e±308※t2(有効15桁) 浮動小数点数(倍精度実数型)
※t1 3.4×10-38~3.4×1038(有効7桁)
※t2 1.7×10-308~1.7×10308(有効15桁)

桁あふれという現象

値が大きいと分かりにくいので、char型で試してみましょう。

例えばunsigned char型は8ビット(1バイト)の領域を占有するため、0~255までの値を保存できますが、それ以上の値を保存することはできません。

unsigned char c;
c = 255; -------------- unsigned char型の最大値を代入
c++; ------------------ cに1を加算
printf("c = %d\n", c);
とすると、cの値は0になってしまいます。これは、型によってあらかじめ確保されたメモリ上の領域から値がはみ出したことで発生する『桁あふれ』と呼ばれる現象です。

同様に、cに最小値の0を代入してそこから1を減算した結果を表示させると、-1ではなく最大値の255となります。

unsigned char c;
c = 0; ----------------- unsigned chart型の最小値を代入
c--; ------------------- cから1を減算
printf("c = %d\n", c);
人間の思考では、このようなことは絶対に起こりません。プログラムでは、メモリ上に確保された範囲によって扱える数値に限界があるのです。


符号付きと符号なしの問題

符号付き(signed)か符号なし(unsigned)かによっても、そこに保存できる値の範囲が変わります。また、小数を保存する場合は、小数点の表現方法によって浮動小数点数(float)と固定小数点数(double)という2種類が存在し、それぞれ扱い方が変わります。

型の制約とは、メモリ上の一定範囲に保存された値の『扱い方』による制約です。

例えばsigned short int型の変数は-32768~+32767までの値を保存できますが、最大値を超えた場合や最小値を下回った場合には、人間の常識を越える値となってしまいます。

その原因は、signed short intのような符号付き整数では『先頭の1ビットが符号を表す』ために使われることです。これが小数となると、その扱い方によって小数点の位置が異なるため、さらに厄介なことになります。

最大値に1加算/最小値から1減算

整数型(char、short int、long int)の場合、符号なし(unsigned)ではすべてのビットを数値の保存に使いますが、符号付き(signed)では先頭の1ビットを符号として使います。先頭ビットが0なら正、1なら負です。

リスト4はsigned char型の変数cに最大値の127を代入し、それに順次1を加算してその値を表示するプログラムのソースです。結果は次のようになります。
c = 127
c+1 = -128
c+2 = -127

リスト5はsigned char型の最小値-128から順次1を減算してその値を表示するプログラムのソースです。結果は次のようになります。
c = -128
c-1 = 127
c-2 = 126

リスト4:signed char型の最大値を超える場合の実験(標準的なCのソース)
#include <stdio.h>

int	main(void)
{
	signed char c;
	c = 127;
	printf("c = %d\n", c);
	c++;
	printf("c+1 = %d\n", c);
	c++;
	printf("c+2 = %d\n", c);
	return 0;
}

リスト5:signed char型の最小値を下回る場合の実験(標準的なCのソース)
#include <stdio.h>

int	main(void)
{
	signed char c;
	c = -128;
	printf("c = %d\n", c);
	c--;
	printf("c-1 = %d\n", c);
	c--;
	printf("c-2 = %d\n", c);
	return 0;
}


型の制約と自然な感覚とのギャップ

例えば、char型ですべてのビットが1である「11111111」は、符号なしでは255ですが符号付きだと-128と読み取られます。変数の型とは、メモリ上のある範囲の値を、処理系がどのように判断するかを規定したものなのです。

このようにコンピュータ上の変数は『型』という制約を負うため、プログラムでの計算は人間の自然な計算の仕方とは異なってきます。プログラミングに慣れてしまえば何でもないことなのですが、逆に人間の自然な考え方から遠ざかってしまう危険性もはらんでいます。

プログラムを作るということは、人と機械との橋渡しをするということです。プログラミングに長く携わっていると、考え方がついつい機械寄りになってしまいがちです。「普通の人間には、これは不自然な考え方なんだなー」と思う感性を大切にしたいと思います。

型名の省略について

なお、符号付きを表すsigned型修飾子は省略できるため、通常は符号なしを示す場合にだけunsigned型修飾子を付けます。表5を参照してください。

また、short intはshort、long intはlongと略せます。intとした場合、そのプラットフォーム(OS)で「最も効率的な符号付き整数」と規定されているため、16ビットOSではsigned short int、32ビットOSならsigned long intとなるのが普通です。

こういった型修飾の煩雑さを避けるため、範囲のさほど大きくない単純な整数を扱う場合には、よくintとだけ記述します。ただ、この記述方法は非常に曖昧なので、このコラムでは型をできるだけ明確に示すことにします。

表5:型名の省略記法
略名 正式名
char signed char
signed short signed short int
short signed short int
signed long signed long int
long signed long int

あとがき


hiropの『ちょっと気になる専門用語』~《値》

プログラミングでは、よく「値」という言葉を用います。コンピュータでは数値だけではなく文字も画像も音声もすべてが数値化して扱われるため、内部で処理されるデータを「値」と表現するのが普通です。

が、普通とは言ってもごく一般の普通の人の感覚からすれば、あまり普通ではありません。業務システムのマニュアルに「値を入力します」と書いてあったので、住所欄に番地だけ入力した……なんて笑い話のような出来事が、昔は本当にありました。

一般には、値とは数値のことであり、文字は値ではないと捉えられています。技術者の側からものを見ていると、世間とのズレが生じることもあります。