苦しんで覚えるC言語

アドレスを記憶する変数


第1項:ポインタという単語

前節では、関数に変数のアドレスを渡すと変数の中身を変更出来ると説明しましたが、
それには、アドレスの値を記憶することの出来る変数が必要になります。
それがなくては、アドレスの値を引数で渡しても受け取れません。

世間では、アドレスの値を記憶する変数をポインタと呼んでいますが、
これは本当はあまり正確な呼び方ではありません。
何故なら、ポインタとは、アドレスを扱う機能3つの総称だからです。
ポインタという呼び方は総称であり、正確には3種類に別れています。

1つ目は、ポインタ型です。
今まで出てきたint型やdouble型と同じような型です。
ただし、ポインタ型の場合、それらとは少し異なる特徴があります。

2つ目は、ポインタ値です。
これは、ポインタ型で扱える数値、要するにアドレスのことです。
整数や実数といった数値の区別と同様に、ポインタ値という区別があるのです。

3つ目は、ポインタ変数です。
これは、ポインタ型で宣言された、ポインタ値を記憶出来る変数のことです。
int型の変数やdouble型の変数と基本的には同じことです。

次からは、これら3つの違いについての説明をしていきますので、
こんがらがってしまわないように気をつけて読んで下さい。

目次に戻る

第2項:ポインタ型

ポインタ型とは、アドレスを記憶する変数の型のことを意味しています。
ポインタ型は、通常の型とは異なる側面を持っています。
それは、ポインタ型は、他の型から作り出される派生型であるという点です。

なにやらわかりにくい説明ですが、実際にはそんなに難しいことではありません。
例えば、int型の場合、これは独立した型です。他の型とは何の関係もありません。
それに対して、ポインタ型は、他の型とポインタ型を合体させて作ります。

int型とポインタ型を合体させると、intへのポインタ型という型が出来ます。
double型の場合なら、doubleへのポインタ型が作り出されます。
また、intへのポインタ型に更にポインタ型を合体させて、
intへのポインタのポインタ型という、多重のポインタ型を作ることすら出来ます。

要するに、ポインタ型は、他の型と合体しなければこの世には存在出来ないという、
いわば、寄生虫のような型なのです。

ところで、ポインタ型は、要するにアドレスを記憶する変数の型です。
アドレスは、この章の始めで表示させたように、結局は整数値に過ぎません。
その整数値を記憶する型が、どうして他の型と合体する必要があるのでしょうか。

これには明確な理由があります。指定アドレスに記憶された数値を取り出すためです。
例えば、int型、double型、char型は、全てサイズが違っています。
int型の場合、標準的なコンパイラでは4バイトのサイズですから、
億単位で存在するメモリというロッカーの4個分を使っていることになります。
int型の値を取り出すには、この4個をまとめて取り出さなくてはいけません。

型によっては、メモリに記憶された2進数の読み方まで違ってくることもあるので、
元々の型がなんだったのかがわからない限り、記憶された数値を取り出せないわけです。

つまり、ポインタ型は、どんな型の変数のアドレスだったかわかる必要があります。
そこで、あらかじめ他の変数と合体した形でポインタ型として作っておけば、
そのポインタ型の変数に記憶された数値は、合体されている型であるとすぐわかります。
voidポインタ
実際には、単独で存在するポインタ型としてvoid型があります。
この型は、どんな変数のアドレスでも記憶することが出来ますが、
元々の変数の型がわからないため、値を取り出すことが出来ません。

目次に戻る

第3項:ポインタ値

ポインタ値とは、ポインタ型の変数が記憶出来る数値のことですが、
これは、要するに、変数のアドレスの値のことです。

多くのコンパイラでは、ポインタ値とは、単なる符号なしの整数値に過ぎません。
その理由は、コンピュータのメモリの仕組みを思い出せばすぐにわかるでしょう。
コンピュータのメモリは、何十億というロッカーの集まりであり、
アドレスとは、そのロッカーにつけられた番号であるからです。

しかし、ここで1つの疑問が残ります。
アドレスが単なる整数値であるなら、int型に記憶すれば事足りるはずなのに、
何故、わざわざポインタ値などという新しい数値として扱う必要があるのでしょうか?

実は、int型に記憶すれば良い、というのは、ある意味ではその通りとも言えます。
32ビットのコンパイラでは、アドレスもint型も32ビットになるので、
符号なしのint型に記憶した所で、何の問題もありません。
それどころか、C言語の先祖であるB言語では、
C言語のint型にあたる型にアドレスを記憶する仕組みだったのです。

しかし、C言語でポインタ値という数値として扱われているのには理由があります。
まず、ポインタ値と通常の整数値とでは、その意味が明らかに異なります。

通常の整数値は、プログラムの中で計算などを行うための数値ですが、
ポインタ値は、計算に使われる数値ではないからです。
つまり、この2つはどちらも単なる整数値ですが、その目的は全く別なのです。

このことから、int型の変数で両方を扱っても、何のメリットもありません。
むしろ、変数に記憶される数値が整数値なのかポインタ値なのかわかりにくくなります。
であれば、いっそのこと、2つを別々の数値とした方がよほど便利になります。

目次に戻る

第4項:ポインタ変数

ポインタ変数とは、ポインタ型で宣言された実際の変数のことです。
この変数には、その元となった型の変数のアドレスを自由に代入できます。
更に、記憶しているアドレスのメモリを読んだり書き換えたりすることが出来ます。

このことから、今までの変数とはかなり違った性質を持つことがわかります。
実際、ポインタ変数は今までの変数にはない機能を備えています。

他の変数は、どんな値を記録するにせよ、なんらかの計算に使うことが目的でした。
文字型のcharですら、その実態は文字番号であり、どの文字を表しているのかを計算するための変数です。

しかし、ポインタ変数では、記憶しているアドレス値を計算に使うことはありません。
ポインタ変数の役目は、それが指し示しているアドレス番号のメモリの値を計算することです。
ポインタ変数そのものを計算に使うのではなく、それが指し示している変数を計算するのが、ポインタ変数の目的です。

言い換えると、普段はポインタ変数として振る舞っているのですが、
指し示している変数の計算が必要な時には、普通の変数に変身する必要があるのです。

この変身機能こそが、ポインタ変数の最大の特徴となります。
ポインタ変数は、ポインタ変数モードと通常変数モードの2モードを備えており、
必要に応じて、それぞれを切り替えて使うことが出来るのです。

ポインタ変数モードでは、たいした機能は備えていません。
具体的には、アドレスへの代入と足し算引き算だけしかありません。
何故なら、ポインタ変数モードに必要なのはアドレスの記憶だけだからです。
アドレスさえ記憶していれば、後は特に何もする必要がありません。

通常変数モードに切り替わった場合、その性質は通常変数と全く同じになります。
おかげで、通常の変数と同様に様々な演算子を使って計算することが出来ます。
当然、その時に使われるメモリは、ポインタ変数モードで記憶したアドレスになります。

今までの変数は、ただ通常変数として使えば良かったのですが、
ポインタ変数では、2つのモードを適切に切り替えて、
使い分けなくてはいけないことを覚えておいて下さい。

目次に戻る