プログラミング コラム

なぜポインタに型があるのか?

講師になって間もない頃、C言語の授業中に生徒から「なぜポインタに型があるのですか?」と聞かれました。
当時の私は、ポインタに型があるのは当然と思っていたので、つい「なぜそう思うのですか?」と逆に質問してしまいました。

その生徒は「アドレスは決まった長さと習いました。ポインタがアドレスを入れるものなら、charやdoubleのポインタというものが不思議に感じます」と答えてくれました。

確かに、アドレスは4(8)Byteなど、決まった長さです。
char型のポインタでも、double型のポインタでも長さに違いはありません。
私はその場で明確に答えることができず、とても申し訳なく、悔しい思いもしました。

普段当たり前に使っているものでも、その本質をどこまで理解できているのか?
他の人に教える立場なら、ただ使えるだけではいけない と実感しました。

今回はそんなきっかけを与えてくれた「ポインタの型」について、考えてみたいと思います。
(32bit環境のC言語を前提としています)

まず、アドレスの大きさは 32bit(4Byte) なので、int型と同じです。
それでは、ある変数のアドレスをint型の変数に代入してみましょう。
int main(void)
{
 int value = 10;
 int address;

 address = &value;
 printf("valueのアドレス = %d\n" , &value);
 printf("addressに代入された値 = %d\n" , address);

 return 0;
}
環境によっては警告文などが出るかもしれませんが、addressにはvalueのアドレスが代入されたことが確認できるはずです。

代入できるということは関数の引数にもできそうです。
void func(int address)
{
 printf("address = %d\n" , address);
}

int main(void)
{
 int value = 10;
 func( &value );

 return 0;
}
こちらの例でも、関数func内で変数valueのアドレスを正しく参照できています。

ポインタを使わずとも、アドレスをやりとりする関数ができました。
ではアドレスを保存する時は「int型変数」でもいいのでしょうか?

ここからポインタの便利さが際立ちます。

上記でも紹介している、関数に変数のアドレスを渡すテクニックでは、引数として渡されたアドレスを間接参照することが重要です。
変数に対して間接参照演算子を使用すると警告が表示されることが多いですが、
次のように強制的にキャストすることで実行可能です。
void func(int address)
{
 printf("address = %d\n" , *((int *)address) );
}
強引ですが、ポインタを使わずにアドレス渡しも実現できました。
しかし、int型の変数では「アドレスが入っている」事はわかっても、
そのアドレスを間接参照した先にどんな値があるかまでは分かりません。
(上記の例では、勝手にint型と解釈しています)

ここで重要なのは「別に良いのでは?」と考えない事です。
確かにポインタを使わなくても、プログラムは作れるかもしれませんが
私たちが追求しなければならないのは、いかに簡潔に、分かりやすいコードを書くかということです。

ポインタに型があるのは「参照先にどんなデータがあるか?」を明確にするためです。
「double * pAddress」と宣言されていれば、参照先はdouble型と分かります。
「int address」だけでは、参照先をどんな型として扱ってよいか、一見して分かりません。

アドレスを間接参照するということは「参照先」こそが重要です。
難しいと言われるポインタですが、実はポインタを使わずに同じ事をする方がよほど難しく、ややこしいものなのです。
written by Tomoji Onishi / 2022.11.18
コラム一覧