|
データ型とポインタ
今回はCの目玉?ともいうべきポインタについてです。いまさらポインタかよ!と思わないでください。今回のトピックは結構重要ですよ。
では、ポインタをおさらいしておきましょう。
int main(int argc, char *argv[])
{
int i = 3;
int *pi = NULL;
char str[32] = "sample";
char *pstr = NULL;
pi = &i;
pstr = str;
printf("i = %d\n", *pi);
printf("str = %s\n", pstr);
return 0;
}
|
piはint型のポインタ、pstrはchar型のポインタです。ポインタも一つの変数です。中身には通常、メモリアドレスが入ります。
メモリアドレスが入りますので、必然的にポインタ変数のサイズは4バイトになります。int型のポインタでも、char型のポインタでも、構造体のポインタでも、全部サイズは4バイトです。32bitのOSであればメモリ空間は4バイトですからね。(これが64bitのOSになると8バイトになるのでしょうか。それはここでは取り上げません。)
ここまではどんなCの参考書にも書いてあります。でもここからはもっと掘り下げてみます。
まず、ポインタはちゃんとした変数です。変数ということは「データ型」を持っています。変数という視点で見れば、intもint型のポインタも同じ扱いで扱わなければいけませんよね。
int型のポインタ変数のデータ型は「int *」です。上のプログラムで言えば、「int *」というデータ型の変数「pi」なんですね。同じように「char
*」というデータ型の変数「pstr」ということになります。これは「int」というデータ型の変数「i」と言っているのと同じ扱いをしているわけです。
「何を当たり前のことを…」と思っているかもしれませんが、結構見落としがちなんですよ。ここは。
ではそれを踏まえて、一つ大事なことを書きましょう。それは「違うデータ型のポインタ変数には互換性がない」ということです。要するに代入できないということです。次のプログラムを見てください。
int main(int argc, char *argv[])
{
int *pi = NULL;
long *pl = NULL;
unsigned long *pul = NULL;
short *ps = NULL;
char *pstr = NULL;
pl = pi; // コンパイルエラー
pul = pl; // コンパイルエラー
ps = pul; // コンパイルエラー
pstr = ps; // コンパイルエラー
pi = pstr; // コンパイルエラー
return 0;
}
|
データ型の違うポインタは互換性がありませんので、上の例ではすべてコンパイルエラーになります。「サイズは全部4バイトで、メモリアドレスが入っているんだから代入してくれてもよさそうなのに」と思うかもしれませんが、サイズが同じであっても、「データ型」が違うので代入できないんです。int
*とlong *でさえ互換性がないんです。
もちろん無理矢理キャストすれば代入できます。それはポインタ変数でない普通の変数と全く一緒です。普通の変数でも互換性がなければ代入できませんよね。
ポインタ変数もデータ型をもったちゃんとした変数だということが、少しはおわかりいただけたでしょうか?Cの入門書のデータ型の紹介のところに、char、int、float...と並んでいるところに、char
*、int *、float *...というのもちゃんと並べて書いておいてほしいものですね。
それにしても、一見ささいな事のように思えますが、なぜこれが重要なのでしょうか。それはC++を使い始めたときに初めてわかります。C++では「データ型」を非常に気にします。ポインタ型のデータ型もCよりさらに高度な使い方をします。「データ型」が1つのテーマだと言ってもいいでしょうね。クラスもデータ型ですので…。
C++を知っている人にとっては、今回のトピックは「ニヤリ」というところでしょうか。この辺がCとC++の間にあるギャップ(C+?)の部分なんですよね。
|