第十八話 ポインタ
「Cで最も難しいって言われてるのはね、ポインタなのよ」
先輩の言葉に、俺と西原さんは答えた。
「ポインタ?」
「うん。まずね、ポインタって意味は、指しているモノってこと」
「指してる……ですか?」と西原さん。
「たとえば、Windowsだったら、デスクトップに『何とかへのショートカット』ってのがよくあるでしょ? ああいうのをイメージしたらわかりやすいと思う。それをクリックしたらアプリが立ち上がるけど、ショートカットそのものは、実際は単なるアプリへのリンクで、それを削除してもアプリそのものが削除されるわけじゃない」
「うん、わかります」
「このポインタは、どこかのメモリーアドレスを指しているわけ。実体は単にアドレスの値を入れてる変数にすぎないんだけど、ある操作をすれば、その指しているアドレスの中の値も間接的に見たり使ったり出来るの」
「ん? ……ちょっと、わかりにくいです」
「だろうねぇ。ここは、コード見た方が、わかりやすいかなー」
先輩はメモ帳にカタカタとコードを書き始めた。
/* ポインタ実験 pointer.c */
#include<stdio.h>
int main(void)
{
int a;
int* pa; /*ポインタ変数の宣言 変数名の前に「*」が付く */
a = 0;
pa = &a; /* ポインタ変数paに、aのアドレスを入れる */
printf("aの値 : %d \n", a);
*pa = 10; /* 「*」を頭に付けると、ポインタ変数paが、
指しているa変数のアドレスと同じ働きになる!
(a変数の値を変えるのと同じ) */
printf("aの値(ポインタ変更後) : %d \n",a);
printf("paの値 : %p \n\n", pa);
printf("aのアドレス : %p \n", &a);
printf("paのアドレス : %p \n", &pa);
return 0;
}
「*paがポインタの変数、ですか?」
西原さんが言った。
「そうよ」
先輩は頷く。
「ちなみに、宣言のときのint* paと、指しているアドレスの値を使う時の*paは、同じアスタリスクマークを使っているけど、意味は全然別だから気をつけて。よく、紛らわしいから違う記号にしろって世界中のプログラマから半世紀近く突っ込まれ続けているんだけど」
「は、はあ……」
「ま、慣れたら簡単だからー。じゃあ、実行するよー」
先輩はgccでコンパイルすると、プログラムを実行した。
C:¥sample> pointer
aの値 : 0
aの値(ポインタ変更後) : 10
paの値 : 0022FF1C
aのアドレス : 0022FF1C
paのアドレス : 0022FF18
C:¥sample>_
「つまり……ポインタを*にして値を変えたら、指している変数の値も変わるってこと?」
「そういう事。ポインタ変数は、通常はアドレスの値を入れている単なる変数だけど、頭に*が付いた時は、中のアドレスの値を表すモードに変わるの。二つの顔があるわけ」
うーん、と俺は唸った。
「ポインタって難しいって先輩は言ってたが、結構わかりやすかったような」
「それは、あたしがイツキくんに、メモリーアドレスについて先にじっくりと教えておいたからでしょうが。この知識無しでポインタ変数を見ても、わけわからないでしょうね」
「そ、そうかも……」
確かに。先輩の教え方が上手かったからかもしれない。
「ところでイツキくん、西原さん。こういうポインタって本当に必要なのだろうか、って思ってない?」
突如、先輩が尋ねてきた。
「あ、うん。ぶっちゃけ」
「私もちょっと疑問が」
「だろうねー。さっきのプログラムだと、使う意味ないし。変数aを変えたかったら、素直にa = 10って書けばいいのよ」
先輩は先ほどのプログラムの存在意義を完全否定した。
「でもね、イツキくんがプログラムを作っているうちに、ポインタの機能がどうしても必要になる時があるの。たとえば……こんなプログラムとか」
先輩は、メモ帳をクリアすると、再びプログラムを書き始めた。
/* ポインタ実験2 pointer2.c */
#include<stdio.h>
/* aとbの値を交換する関数 */
void swap(int a, int b)
{
int temp; /* 一時的にaの値を入れておく変数 */
temp = a;
a = b;
b = temp;
}
int main(void)
{
int a = 10;
int b = 20;
printf("a = %d , b = %d \n", a,b);
swap(a,b); /* aとbを交換? */
printf("a = %d , b = %d \n", a,b);
return 0;
}
「これは、swap関数で、変数aとbの値を交換するプログラム。これはポインタを使わなかった場合」
「あ、確かに」
「でも、よーく考えてみて。これだと、実は何の意味もないのよ」
俺と西原さんは「えっ!?」とハモってしまった。
「ま、実行してみればわかるか」
先輩はコンパイルするとプログラムを実行した。
C:¥sample>pointer2
a = 10 , b = 20
a = 10 , b = 20
C:¥sample>_
「……値が、変わっていない!?」
「よく考えてみて。変数aとbは、まず10と20の値になって、swap()関数に引数として渡されるんだったよね。だったらもう、main関数の変数aとbはswap関数内では何の関係も無いじゃない。いくらswap関数内で同じ名前の違う変数を交換してても、main関数の方の変数はそのままだし」
「あ、そうか」
俺は気づいた。確かにこれだと、aとbは、変えられない。
「じゃ、イツキくんは、他にどうすれば値を変えられると思う?」
「うーん……戻り値を使えば……」
「それだと、一つしか返せないよー」先輩はにやにやと笑う。
「ダメか……」
俺はギブアップした。
「私も、わかりません」と西原さんも頷く。
「で、ここで、ポインタ変数の出番! こうしたら、使えるのよ」
/* ポインタ実験3 pointer3.c */
#include<stdio.h>
/* aとbの値を交換する関数 */
void swap(int* a, int* b)
{
int temp; /* 一時的にaの値を入れておく変数 */
temp = *a;
*a = *b;
*b = temp;
}
int main(void)
{
int a = 10;
int b = 20;
printf("a = %d , b = %d \n", a,b);
swap(&a,&b); /* aとbを交換! */
printf("a = %d , b = %d \n", a,b);
return 0;
}
「ここで、main()関数で呼び出している側は、aとbをポインタ変数にしていないのにも注目ね。単にint型の変数のアドレス値がswap関数側で分かればいいのだから、頭に&を付ければ充分ってわけよ」
「これだと変数a、bのアドレスが渡されるから──その値を変えられる」
「じゃ、実行するわよ」
先輩が再び実行する。
C:¥sample>pointer3
a = 10 , b = 20
a = 20 , b = 10
C:¥sample>_
「おぉっ」
「値、変わりましたねー」と西原さん。
「でしょ? ま、ポインタの使い道って、こういう関数の引数関連が多いわね。でも、他にもいろいろと使い道があるから、そのたびに教えるわね」
「はい」
「で、他にもポインタには変わった機能があるから、次はそれを教えるわ。ポインタは、足したり引いたりも出来るのよ」
「なっ!?」
先輩は、さらに説明を続けた。