ポインタ "&" と "*" の相関関係

MINI SERIES


ポインタとは

C/C++ で絶対に避けてとおることのできないポインタ。とりあえずポインタについてのお話です。

コンピュータはデータをメモリという記憶装置に書き込むことで記憶します。そして記憶装置からデータを読み出して利用しています。メモリは基本的に 0 から 255 までの数値を表すことができる 【バイト】 というブロックが集まってできています。そのブロックの集まりを 【メモリ空間】 、そしてメモリ空間のどのブロックかを特定するのに 【アドレス(番地)】 を使用するものと考えてください。

メモリ空間には、0 からの通し番号で番地がつけられていて、上限はさまざまです。現時点では一般的なコンピュータが ”メモリ 64 MB” となっていますので、この場合は 64,000,000 バイト。つまり、0 から 255 までの数値を記憶できるブロックが 6400 万個も搭載されていることになります。

この広大なメモリ空間の場所を示すのにアドレスを利用して、「***番地」 というようにあらわします。たとえははじめから 1000 番目にあるブロックの値を読み出したい場合には 「999 番地」 というようになります。アドレスは 0 から始まるので 1000 番目は 999 ですが、その辺はあまり気にしなくても大丈夫だと思います。

上記で述べた 「***番地」 の *** という値を保存した変数のことを 【ポインタ】 といいます。そして 【ポインタが指す値】 といえば、***番地のブロックに記憶されている値のことを示すことになります。

 

さまざまな入れ物

第1節ではブロックという言葉で扱いましたが、このデータを保存するためのブロックは C/C++ にはたくさんの種類が存在します。基本的には上記で述べたとおり、0 から 255 までの数値を格納できる 【バイト】 という構造なのですが、バイト単位を複数利用することによって、また保存されている値を取り出す方法を変えるなどによってさまざまな値を表現しています。

一番単純なのが、unsigned char というデータ型。これは上記の 【バイト】 そのものです。つまり 0 から 255 までの値を保存することができるということになります。一般的には char 型を用いることが多いのですが、コンピュータでは一文字を記憶するのにこの char 型を利用します。0 から 255 までの範囲内で、65 ならば "A"、66 ならば "B" といった感じで、特定の値に対して文字が割り当てられています。

ほかに unsigned short というデータ型があります。これは 2 バイトをひとまとまりとしたデータ型で、0 から 65535 までの値を保存することができます。double というデータ型は、8 バイトをひとまとまりとして利用し特殊な格納方法によって、大規模な実数値を表現することができます。

 

C++ の配列と文字列

第2節で述べたさまざまなデータ型を複数集めて、さらに大きなブロックを形成することができます。これを C/C++ では 【配列】 と呼んでいます。

char 型は一文字を記憶するのに利用します。これを複数個まとめて配列にすることによって文字列を表現することができます。ただし、C/C++ は基本的に文字列という概念がないので、char 型の配列として操作することになります。もちろん、C/C++ には文字列操作用の関数が存在しますが、それは char 型の配列を文字列として操作できるように処理を行っているだけです。

配列はある一定のデータ型を複数個集めたものです。この複数個集めたもののうち、特定の場所を示すのにもポインタが使用されます。配列を定義した時点で配列が保存されている場所の先頭アドレスが獲得できます。あとは、そこから何番目かを指定すれば、目的のデータへアクセスすることが可能になるわけです。

 

ポインタの使い方と表現

ポインタを使用するにあたって、覚えておかなければならない記号があります。それが "&" と "*" です。これをしっかりと理解すると、ポインタが非常によくわかるようになります。C/C++ を極めようと思ったら必須ですので、ぜひがんばってみてください。

一般的に、"&" はアドレス、"*" はをあらわします。

具体的にいいますと、たとえば以下のような変数が宣言してあるとします。

int iData

このとき、変数 iData は、int 型のを保存できる変数となります。

ここで第1節の内容を思い出してください。iData に int 型のを保存できるということは、実際にその値を保存する場所がメモリ空間内に存在していることになります。メモリ空間の場所を示すのにアドレスが利用されていることは、第1節で述べました。この、保存されているアドレスを得たいときに、

&iData

というように書きます。

"&" の作用は、この記号の右側に書かれた変数名が値を保存するアドレスを獲得することです。これによって、実際にメモリ空間のどの部分へデータを書き込むのかがわかります。なお、ここで利用している "&" という記号は 【参照】 とは別の意味になりますので、【参照】 をしっているかたは混乱しないように注意してください。

また、上記の "&" とは逆の性質を持つ記号があります。

難しいことは考えないで、とりあえず以下のような変数が宣言されているものとしてください。

int *pData

これは、一般的に pData が int 型へのポインタであることを示します。もうすこし具体的に述べますと、pData という名の変数にはアドレスが記憶されていて、そのアドレスには int 型のデータが格納されているということを表しています。

pData は int 型のデータが保存されているメモリ空間の場所を示すポインタです。この pData が示す場所に格納されている値を取り出したい場合には以下のようにします。

*pData

"*" の作用は、この記号の右に書かれた変数名が指しているメモリに記憶されている値を獲得することです。これによって、指定されたメモリのデータを操作することが可能となります。

なお、上記で pData にはアドレスが記憶されていると書きました。つまり、pData 自身はアドレスを記憶する変数なのです。ここで &pData とすると、pData に記憶されているアドレス値が保存されている、メモリ空間上のアドレスを獲得することができます。

この、"&" と "*" はまったく正反対の動作をします。たとえば次の例を見てください。

*(&iData)

これはどういうことになるでしょう。

まず C/C++ は括弧の中が優先されるという約束がありますので、&iData がはじめに処理されます。ここで、iData の値を記憶するアドレスを獲得することができました。そして、次に "*" が適用されます。すると前記で獲得したアドレスが示す場所に記憶されている値を参照することになります。つまり、iData の値そのものが得られます。

これは、単に iData と表記したときとまったく同じ結果が得られたことになります。

これで、"*" と "&" は正反対の性質があることがわかっていただけましたか?まだ難しいようでしたら、実際にプログラミングをしながら考えてみるとわかるかもしれません。

 

ポインタ表現の読み方

"&" と "*" の記号の役割を紹介したとこで、第4節で後回しにしてあったデータ定義等の読み方を紹介してみたいと思います。

まずは基本中の基本。

int iData

これは約束事ですので覚えてください。 「iData は int 型です」 という意味になります。つまり iData は int 型のデータを記憶する変数のことですよね。

続いて第4節で後回しにしていた宣言。

int *pData

読み方は "*" の記号の意味を忠実に適用してください。もう一度復習しますと、「右の変数が指す値が int 型」 という意味でしたよね。そうすると、

pData が指す値が int

ということになります。つまり、int 型の値を指す変数 pData は int 型へのポインタということです。

これはなれないと非常に難しいそうです。pData はポインタとして宣言しているのに、宣言文の中には一般にアドレスを示す "&" が現れてこないのですから納得がいかなくてもむりはありません。

それでも、右から解釈することと、"&" と "*" は正反対の意味であることをしっかりと理解すれば、きっと納得がいくと思います。

逆に

int &pData

というのはどうでしょう。

実際に Microsoft Visual C++ 6.0 Enterprise を利用してコンパイルにかけてみると、「参照が初期化されずに宣言されています」 というエラーメッセージが現れました。ちょっと難しい表現ですが、”参照” とは "&" 記号のことをいいます。これは pData の値が保存されているアドレスを参照すると int 型のデータが得られますという意味を "&" が示していますが、肝心の pData がまだ定められていない(初期化されていない)ということです。

これは、宣言文では "&" という記号が 【参照】 として取り扱われるために発生します。参照もポインタと似た存在ではありますが、少し性質が違ってきます。参照につきましてはまた別の機会に取り扱おうと思います。