C99にいろいろといちゃもんを付けるシリーズ、今回は前回に引き続き「_Bool編」をお送りいたします ←いや、先週の内容に関するメール等の反応がいくつかありましたので、それをネタにしつこくやってみようか、などと思ったわけです。
まず、「そんなに_Boolが気に入らないなら使わなけりゃいいじゃん」という反応。そりゃまあそうなんですが、プログラムは必ずしも「自分で作って、自分で直して、自分で使う」とは限らないわけですし、どちらかと言えば小さ目のCの言語仕様を肥大化、複雑化させるのはCらしさが失われるんじゃないかな、と思うわけです。
それから、「_Bool型は1ビットで表現できるわけだから、メモリの使用効率がよくなるのでは?」という指摘も頂きました。ただ、これは現実の実装ではどうなんでしょう? たとえば、
_Bool a, b;
とした場合、この変数aやbに割り当てられるのは1ビットのメモリのみ、同じ1バイト中に変数aとbが同居することも可能、という実装はありなのか、ということです。私が規格書(JIS X 3010:2003)を見た範囲では「こうすべき」というような規定は見当たりませんでした。もし上記の定義で、変数aとbが同じ1バイトに割り当てられたりすると、
_Bool a, b; _Bool *p = &a, *q = &b;
なんてやると、aとbは同じアドレス(の異なるビット)に配置されてますから、pとqに入るポインタ値はいかに?
というような問題が発生します。さらに、_Bool型の配列を確保して、
_Bool c[8]; _Bool *r; for (r = c; r < c+8; r++) ・・・
なんてことやると、rに入るポインタ値の内部表現がアドレスだったりすると、うまく動かないのは自明です。
今の大概のコンピュータは「バイトマシン」でメモリの1バイトに対してアドレスが振られています。つまり1バイト内の各ビットを表現するためにはアドレスに加えて「ビット位置」を表すための値が必要ということになります。
もちろん、Cの「ポインタ」は規格上、内部表現として必ずしもアドレス値を用いる必要はない(実装依存)ということと、各ポインタ型によって内部表現が異なっていても構わない(その昔の16ビット時代のDOS/Windowsには「メモリモデル」というのがあって、ポインタ値の内部表現が型によって違っていることもあった)ことになっています。これを利用して、_Boolへのポインタ型については、他のポインタ型の内部表現とは変えて、「アドレス」+「ビット位置」というものを採用すれば、上記の例のpとqの値が同じになってしまうような問題は避けることができます。
ただし、現実問題として、こんなややこしい実装をするでしょうか。たとえば、アドレスに32ビット必要なバイトマシン(1バイトは8ビットとする)において、メモリ上の各ビットを特定するための情報は「32ビット(アドレス)+3ビット(ビット位置)」で、計35ビットということになります。いかにも半端ですね、これ。「_Bool型のポインタは35ビットです」って。これを現実に実装しようと思えば、無駄が生じるのは承知の上で、40ビット(5バイト)にするか、アライメントの面から思い切って64ビット(8バイト)にしてしまうでしょう。
となると、せっかく無駄を省くために_Bool型の使用するメモリを1ビットとしたのに、その_Bool型を指すポインタには無駄を承知で5〜8バイト必要になるは、他のポインタ型と内部表現が異なっているために取り扱いは面倒だは、いいこと無いように思えます。だいたい、いま時メモリなんてふんだんにあるんだから(組み込み系などの事情は不明)、わざわざ_Bool型は1ビットなんてセコいことせずとも、1バイトでも4バイトでも、好きなだけ割り当てた方がよさそうです。
さて、現実の実装はどうなっているのかいな、というわけで調べてみました。C99に対応してそうなコンパイラを手持ちで探してみたところ、gcc 3.2.2がそれなりに対応しているようなので、これで確かめてみました。
#include <stdio.h> main() { _Bool a, b, c[8]; int i; printf("sizeof a = %d\n", sizeof a); printf("sizeof c = %d\n", sizeof c); printf(" &a = %p\n", &a); printf(" &b = %p\n", &b); for (i = 0; i < 8; i++) printf(" &c[%d] = %p\n", i, &c[i]); }
なんてプログラムを実行させてみたところ、
sizeof a = 1 sizeof c = 8 &a = 0xbffffa0b &b = 0xbffffa0a &c[0] = 0xbffffa00 &c[1] = 0xbffffa01 &c[2] = 0xbffffa02 &c[3] = 0xbffffa03 &c[4] = 0xbffffa04 &c[5] = 0xbffffa05 &c[6] = 0xbffffa06 &c[7] = 0xbffffa07
というわけで、gcc(3.2)では_Bool型は1バイトという実装になっているようです。
結局、
#define _Bool unsigned char
とか、
typedef unsigned char _Bool;
とかやったのと大差ないような気がするんですけどねえ。違いがあるとすれば、_Bool型の変数には絶対に0と1以外の値が入らない、つまり、
_Bool a; a = 5;
とかやっても、変数aに入るのは1であることが保証されている、ということくらい。
というわけで、今週も_Boolについてしつこくしつこく考察してみたわけですが、
「やっぱりいらないよ、この_Boolって型。別にあっても構わないかも知れないけど『いまさら』という感じしかしないよなあ。C89が制定されたときあたりに追加されていたならまだしも…」