例え話抜きにC言語のポインタを理解する その1

C言語やC++を勉強し始めた人にとって、最初の大きな壁といわれるのが「ポインタ」。

ポインタが分かれば、その先の勉強は結構進めやすくなる。

でも、それまでintやdoubleで「整数」や「小数」を扱ってきた初心者にとって、int*やdouble*などの「ポインタ」はとてもややこしく映る。

結局、intとint*は何が違うの?int*もdouble*もポインタってことは両方とも一緒?*は何のために使うの?などなど、このシリーズではポインタの基本を説明していく。

これは私の考えだけど、ポインタを説明するときによくありがちな過剰な例え話は逆に初心者の理解の妨げになると感じているので、この記事ではできるだけそうした例え話を必要最小限にとどめ、ポインタに関係するところに絞って説明していく。

もくじ

メモリ

「メモリ」というものを聞いたことはあるだろうか。「主記憶装置」とも呼ばれる。

だいたいこんな見た目をしていて、

デスクトップPCの場合、こんな風に取り付けられている。

画像:ドスパラ大百科様(http://dospara-daihyakka.com/jisaku/manual_03.html)より

このメモリというものは、PCの電源が入っている間は色々な情報をため込んでおける場所になっている。

皆さんがC/C++プログラムを動かしている間は変数や関数は実はこのメモリ上に存在している。

アドレス

メモリはこんな風に同じ大きさで仕切られたものになっている。

灰色の四角形1個に、1バイトのデータが置いておける。例えばint型は4バイト
(コンピュータの機種によって異なる場合がある) の大きさがある型なので、int型の変数を1つ作ると、メモリ上の4バイト分の領域が使われる。この領域を使って、プログラムは「123」などの整数値を覚えておける。

char型の値 ‘a’ の場合は1バイト分使われる。

ただ値をメモリに置いただけではメモリのどこに値を置いたのかが分からず、プログラムは後からその値を使うことができない。

そこで必要になるのがアドレスだ。

実はメモリにはデータを置いておくすべてのスペース(上図でいうところの灰色の四角形)に、1000、1001、などの数値があらかじめ割り振られている。

プログラムで変数に数値を設定した場合、メモリ上に数値が記録されるが、同時にどこに記録したのかがアドレスとしてコンピュータ側で把握されている。

アドレスは、プログラマーも簡単に確認できる。例えば次のコードを実行してみよう。

#include <stdio.h>
int main(void){
    int a = 123;
    printf("%p", &a);
}

このコードでは、int型の変数aを値123で初期化し、変数aのアドレスをprintfで表示している。

%pはアドレスを表示するために必要なフォーマット指定子で、&aは変数aの先頭のアドレスを意味する。

私のPCで実行すると「00000003B6EFFC44」という結果が表示された。これは「16進数で00000003B6EFFC44番目のメモリ領域」という意味になる。 00000003B6EFFC44番目から4バイト分がint型の数値に使われている。

ただ、おそらく皆さんの実行結果とは異なるアドレスが表示されたはずだ。

メモリは、当然他のプログラムも使っている。今実行されているプログラム(Webブラウザ、ワード、エクセル、Skypeなどなど)や開いているページはコンピュータにより異なるので、変数に割り当てられるメモリの領域も当然異なる。領域が異なれば変数のアドレスも異なるのだ。

だから、アドレスはPCによっても異なるし、同じPC上でもプログラムをどんな状況で実行したかで全然違ってくるのが普通だ。

変数はメモリ上に存在し、変数の在り処はアドレスで管理されている。

アドレスを扱う変数

ところで、先ほど%pで表示したアドレスは変数に代入して使える。例えばこんな風に。

#include <stdio.h>
int main(void){
    int a = 123;
    int* address = &a;
    printf("%p", address);
}

アドレスが1つ表示されるはずだ。

int* で宣言した変数には、int型変数の先頭のアドレスを入れておける。

配列でも試してみよう。

#include <stdio.h>
int main(void){
    
    int a[3] = {123, 124, 125};
    
    int* address0 = &a[0];
    int* address1 = &a[1];
    int* address2 = &a[2];
    
    printf("%p\n%p\n%p\n", address0, address1, address2);
    
}

&a[0]は、a[0]のアドレスという意味だ。3つのアドレスが表示されたと思う。私が今試しに実行してみたらこんな結果になった。

00000091B572F958
00000091B572F95C
00000091B572F960

上から順に、a[0]、a[1]、a[2]のアドレスとなっている。

それぞれのアドレスの差に注目して欲しい。a[0]とa[1]のアドレスの差は何バイト分だろうか。Windowsなら標準の「電卓」アプリの「プログラマー」機能で計算できる。

4バイト分の差があるという計算結果が出た。

int型は4バイトの整数の型なので、a[0]のすぐ隣りにa[1]があることが分かる。同様に、a[1]のすぐ隣りにはa[2]がある。

一方、double型は8バイトの大きさがあるので、配列にした場合、8バイト間隔で各要素が並ぶ。気になる人は試してみてもよいかも。

配列の場合、配列の各要素はメモリ上で連続して並んでいる。

ちなみに、アドレスを持たせた変数も他の変数と同様にメモリ上に存在する。64bitのPCなら変数のサイズは8バイト、32bitのPCなら4バイトとなっている。

64bitの場合のイメージ。アドレスを扱う変数は8バイトの大きさがある。図におさまらなかった・・

そして、アドレスを扱う変数のことを私たちは「ポインタ」と呼んでいる。

今回はポインタとは何者なのかについて説明してきた。

次回は、ポインタを使ったらどんなことができるのか、について説明していく。

次回はこちら

コメントする