C言語の難関と呼び声高い「ポインタ」。数多くのC言語初学者を奈落の底へと落としていく「ポインタ」。
そんな「ポインタ」ですが、実は理解するにはコツというか、理解する順序があり、そこを抑えておけば「ポインタ」が意外と単純なものであることがわかります。
ここではそんな「ポインタ」の考え方について、図とコードも交えながら解説するので、ぜひ参考にしてみてください。
ポインタを理解するコツ
ポインタをスッキリと理解するためには、以下の順序で理解する必要があります。
- 変数とアドレス(メモリ)の関係
- ポインタ(ポインタ変数)
自己学習だと「変数とアドレス(メモリ)の関係」がすっ飛ばしがちで、いきなりポインタが出てくるため、
ポインタなにそれおいしいの?
てか、意味わからんのだが?
という事態に陥ります。そして、「変数とアドレス(メモリ)の関係」を理解してしまえば、ポインタの8割は理解したと言っても過言ではありません。
というわけで、まずは「変数とアドレス(メモリ)の関係」について解説します。
変数とアドレス(メモリ)の関係
プログラミング学習を始めると、以下のように変数定義をすると思います。
int main(void)
{
char a; /* char型で変数aを定義 */
a = 'a'; /* 変数aに 'a' を格納 */
int b; /* int型で変数bを定義 */
b = 10; /* 変数bに 10 を格納 */
…
}
この変数の定義がメモリ上どうなっているのかイメージできますか?
上記定義のメモリ上のイメージは以下のようになります。(以降の説明では、intが4byteの処理系でアドレス幅は64bitとする)
変数名を付けた領域は、変数名を使って値を読み書きすることができます。
また、領域のアドレスを取得したい場合、 &(アンパサンド) を変数名の前に付けることで取得できます。
コードで見ると下記のようになります。
#include <stdio.h>
int main(void)
{
char a; /* char型で変数aを定義 */
a = 'a'; /* 変数aに 'a' を格納 */
printf("value of a : %c\n", a); /* 変数aに格納されている値を表示する */
printf("address of a : %p\n", &a); /* 変数aのアドレスを表示する */
return 0;
}
このコードを実行すると結果は以下のようになります。
(アドレスはイメージに合わせた番地にしています。アドレスが実行毎に割り当てられる領域が変わるので、必ずしも "0x7fffff00000000" になるというわけではありません。)
root@radical:~/test# ./a.out
value of a : a
address of a : 0x7fffff00000000
ポインタ(ポインタ)変数
ポインタ変数は以下のように定義します。
int main(void)
{
char a; /* char型で変数aを定義 */
a = 'a'; /* 変数aに 'a' を格納 */
int b; /* int型で変数bを定義 */
b = 10; /* 変数bに 10 を格納 */
/* ここからポインタ変数の定義 */
char* pa; /* ポインタ型で変数paを定義、値にアクセスする際はchar型とする */
pa = &a; /* 変数paに変数aのアドレスを格納 */
int* pb; /* ポインタ型で変数pbを定義、値にアクセスする際はint型とする */
pb = &b /* 変数pbに変数bのアドレスを格納 */
…
}
このコードからも分かる通り、「ポインタ変数」はアドレスを格納する変数になります。
メモリ上のイメージは以下のとおりです。
「変数とアドレス(メモリ)の関係」でも見たとおり、アドレスのサイズはchar型の変数だろうが、int型の変数だろうが変わらないため、変数paと変数pbのサイズは同じです。
じゃあ、*(アスタリスク)前のcharやintは何なの?
という疑問が浮かぶと思いますが、このcharやintは後述するポインタ変数に格納されているアドレスの値にアクセスするときの型となります。(実際はアクセスだけでなく、アドレスの加減算にも関わってきますが、それは一旦置いておく)
ポインタ変数にアドレスが入っていることのイメージはできたので、あとはポインタ変数に入っているアドレスが指す値へアクセスする方法さえ覚えれば、ポインタをマスターと言っても良いでしょう。
"ポインタ変数に格納されているアドレス” が指す領域の値へアクセスするときは以下のようにします。(下記コードは実際にコンパイルして実行できるので、実行して試してみることもできます。)
#include <stdio.h>
int main(void)
{
char a; /* char型で変数aを定義 */
a = 'a'; /* 変数aに 'a' を格納 */
int b; /* int型で変数bを定義 */
b = 10; /* 変数bに 10 を格納 */
/* ここからポインタ変数の定義 */
char* pa; /* ポインタ型で変数paを定義、値にアクセスする際はchar型とする */
pa = &a; /* 変数paに変数aのアドレスを格納 */
int* pb; /* ポインタ型で変数pbを定義、値にアクセスする際はint型とする */
pb = &b; /* 変数pbに変数bのアドレスを格納 */
/* ここからポインタ変数に格納されているアドレスが指す領域の値へアクセスする処理 */
char a_dash; /* char型で変数a_dashを定義 */
a_dash = *pa; /* 「変数paに格納されているアドレス」が指す領域の値を、変数a_dashに格納 */
int b_dash; /* int型で変数a_dashを定義 */
b_dash = *pb; /* 「変数pbに格納されているアドレス」が指す領域の値を、変数b_dashに格納 */
printf("a_dash:%c, b_dash:%d\n", a_dash, b_dash);
return 0;
}
このコードの実行結果は以下のようになります。
root@radical:~/test# ./a.out
a_dash:a, b_dash:10
メモリ上のイメージは以下の通りです。(図が煩雑になるためa_dash側のみの説明ですが、b_dash側も同様の考え方です)
まとめ
C言語のポインタは、プログラムを組む上で強力なツールですが諸刃の剣でもあります。
しかし、ここで解説したようにメモリのことをイメージして使えば、使いこなすことができるでしょう。
そして、ポインタを使うことで、プログラムだけでなくコンピュータに対する理解も深まり、より高度なプログラムを組んだり、バグに対するアプローチの幅を増やすことができます。
もっともっとポインタを使って、コンピュータのことを理解してみてはいかがでしょうか。
コメント