僕なりにポインタの説明を考えてみた。
現在研修で新入社員にC言語を教えてるのですが、やっぱポインタは鬼門。
分かってしまえばどーってことないのだけど、あの特殊な文法は迷わせるよね。
特にポインタ変数を宣言するときは
int *a;
って宣言して
で、「ポインタはアドレスだよ」と習うので、
aのアドレスを表示させようとして
printf("%p", *a); // ERROR
と書いてコンパイラに怒られてしまってどうしようもなくなってしまったり。
ただ文法もそうなのだけど、それ以上に概念がつかめない人が多いこともわかった。
特に今は大学で「プログラムやってました!」って人も
MATLABでシミュレーションだったりWeb系の言語だったりで
メモリを叩いたりする経験がないことが多いので
いきなり「これがメモリの中身で、そのなかにアドレスが振られててさー」と言っても
イメージすることが難しいみたい。
で、どう説明するものかお風呂の中でボーッと考えてたら
なんかイメージが浮かんだので書いてみる。
古参の方から見ればゆとりに感じてしまうかもしれない例えかもしれませんが。
もし間違いをしていましたらお手数ですがご指摘いただけると助かります。
解説文なので間違いがあったら多くの人に迷惑をかけるので
致命的な間違いがあれば引っ込めます。
変数の型はファイル形式だ
intとかcharとかfloatとかstructとか配列とかいろいろあるけど
ここでは「ファイルの形式だ」とイメージしよう。
パソコンの中ではtxtとかdocとかmp3とか
いろいろ収納しているデータによってファイルの形式が違うよね。
ポインタ型は「エイリアス」「ショートカット」だ
じゃあここでポインタ型にあてはまるるものはなにかというと
Macだったら「エイリアス」、Windowsだったら「ショートカット」
をイメージしてほしい。
以下Windowsユーザは「エイリアス」を「ショートカット」に読み替えること。
Macで適当なファイルのエイリアスをつくって
そのエイリアスのファイルの情報を見てみよう。
僕はiTunesにいれているPerfumeのmp3ファイルでエイリアスを作ってみた。
まず注目して欲しいのは「オリジナル」のところ。
このPerfumeのポリリズムのmp3のファイルの本体は
/Users/kskktk/Music/iTunes/iTunes Media/Music/Perfume/Polyrhythm/Perfume - Polyrhythm.mp3
って場所にあることが分かる。
またあとで説明に使うから「場所」のところも確認しておいてほしい。
このポリリズムのエイリアスファイルはデスクトップにおいてるってことが分かる。
エイリアスファイルもファイルだから
そいつ自身にもありかがあるのは当たり前っちゃー当たり前。
エイリアスファイルはファイルそのものではない
ここで気をつけるべきことは
エイリアスファイルはそのファイルそのものではないということ。
このエイリアスファイルをクリックしたらポリリズムが流れるだろうけど
それはエイリアスファイルが「オリジナル」で指し示しているファイルを選択して
その先に格納されているmp3データを再生してくれてるのだった。
エイリアスファイルをデスクトップに作成しても
そのオリジナルのファイルがコピーされてくるわけではない。
エイリアスファイルを作成するってことは
オリジナルファイルがどこにあるかを覚えているファイルを作っただけ。
エイリアスファイル自体にはテキストも音楽も画像も保存できない。
あくまで保存できるのはそのファイルがどこにあるかだ。
ポインタ型変数の宣言
さて今まで上で説明したイメージでCのポインタの文法を考えてみよう。
ポインタ型の宣言を考えてみよう。さっきも書いたこれ。
int *a;
これは「int型を指すポインタ変数a」を宣言したんだった。
ここで注意すべき、初心者が間違える落とし穴がここにあって
上の宣言で作った変数はあくまでも"a"って変数で
決して"*a"って変数を作ったのではないということ。
「aの中にアドレスを入れますよ」という意味で*を付けただけ。
型宣言だと思えばいい。
これがCの文法の分かりにくいところだよね。
「え?サンプルプログラムの中では*aって変数出てきたよ?」って思った人も
それはあとで説明するからそのまま読み進めてほしい。
さてこれを先ほどのイメージで読み替えると
mp3ファイルのありかを保存できるエイリアスaを宣言した
みたいなイメージだ。
aはあくまでもエイリアスだからファイルのありかしか保存できない。
つまりポインタ変数aはアドレスしか保存できないってことと同じ。
具体的な値を代入したり、その値そのものを見たりすることはできないんだった。
ただエイリアスファイルは
mp3ファイルだろうがdocファイルだろうがなんでも一つで扱えたけど
Cでポインタ変数を定義するときは
そいつがなんのアドレスを保存できるかってのを
明示的に知らせてあげないといけないってルールがある。
int *a;
と宣言したときは、aが保存できるのはint型のデータが格納されているアドレスだけ。
float型のデータが格納されたアドレスを保存したいときは
float *b;
って宣言しないといけない。
上のイメージを擬似的にコードにしてみると
mp3が保存されているファイルのありかを保存するエイリアスaを宣言するときは
mp3 *a;
docファイルが保存されているファイルのありかを保存するエイリアスbを宣言するときは
doc *b;
と書くようなイメージかな。
*演算子と&演算子
さてここまでの説明を踏まえて
みんなを悩ませる*演算子と&演算子を上のイメージで解説する。
まずは文法的な定義を復習しよう。
これをさっきのエイリアスのイメージで書くと
- *aと書くとファイルのありかにあるデータそのものにアクセスできる
- &aと書くとファイルのありかを教えてくれる
といった感じのイメージだと思えばわかりやすいかな。
以下のCの(わざとらしい)ソースコードで動きを考えてみよう。
#include<stdio.h> int main() { int *a; int b; a = &b; *a = 5; printf("%d\n",*a); printf("%p\n",a); printf("%p\n",&a); return 0; }
これを説明のためにさっきのパソコンのファイルイメージを擬似言語で書き換えてみる。
(あくまでもイメージなので細かいところはツッコまない!)
main() { txt *a; txt b; a = &b; *a = "こんにちは"; print(*a); print(a); print(&a); return 0; }
どういうプログラムなのか一行づつ逐次解説していこう。
txt *a;
これはテキストファイルのありかを保存できるエイリアスaを作成したというイメージ。
aにはファイルのありかを保存できる。
この時点では宣言しただけなのでaはどのファイルのありかも入っていないね。
txt b;
これはテキストファイルbを作成したというイメージ。
ここではbはテキストファイルそのものだね。
普通のコンピュータと違うのは
新しくファイル作成するときも
自分でファイルをどこに置くかを決めることは出来ずに
コンピュータが勝手に置き場所を決めちゃうってこと。
a = &b;
さっきの定義を思い出そう。
&をつけると「ファイルのありか」を教えてくれるんだった。
つまり&bとするとテキストファイルbのありかを教えてくれる。
aはファイルのありかを保存できるんだったから
この式は
「エイリアスaにテキストファイルbのありかを覚えさせろ」
ってことね。
*a = こんにちは;
またさっきの定義を思い出そう。
*をつけると「ファイルのありかにあるデータそのものにアクセスできる」んだった。
aにはテキストファイルbのありかが保存されているんだったね。
じゃあ*をつけたらそのファイルのありかにあるデータにアクセスできるってことだね。
つまりこの式は
「エイリアスaに保存されたファイルのありかにあるテキストファイルに"こんにちは"と代入しろ」
ってこととなる。
以上がイメージできればここからは簡単。
printf(*a);
*aだからエイリアスaが保存しているファイルのありかにあるテキストファイルの内容そのものにアクセス出来るんだね。
ということは上でエイリアスaが指すテキストファイルに"こんにちは"を代入したのだから
ここでは「こんにちは」が表示されるね。
printf(a);
*も&もついていないただのa。
エイリアスaにはファイルのありかを保存されているのだった。
今エイリアスaにはテキストファイルbのありかが保存されているのだから
ここではそのまま「テキストファイルbのありか」が表示されるね。
printf(&a);
&だから「ファイルのありか」だね。
&aってことはエイリアスaのありかをってことだから
ここでは「エイリアスaのありか」が表示される。
上のポリリズムの例で考えると
aはエイリアスが保存しているファイルのありかだから
/Users/kskktk/Music/iTunes/iTunes Media/Music/Perfume/Polyrhythm/Perfume - Polyrhythm.mp3
が表示されて
*aはエイリアスが指しているファイルそのものだから
ポリリズムが再生されて
&aはエイリアスそのもののありかだから
/Users/kskktk/Desktop
が表示されると思えばいいね。
一旦まとめ
さてここで上記の例を踏まえていったんここで要点を整理しよう。
txt *a;
と宣言したとき
txt a;
と宣言したとき
- aはtxtデータを保存できるファイルである。
- &aはaに格納されているファイルのありかを示す。
- aにはファイルのありかではなくデータが入っているのだから、*aは意味が無いし使えない。
これにならって擬似言語からC言語での説明に移ると
int *a;
と宣言したとき
- aはintを指すアドレスを格納できる変数(つまりポインタ変数)である。
- &aはポインタ変数そのもののアドレスを示す。
- *aは格納されたアドレスが指し示すデータそのものを示す。
int b;
と宣言したとき
- bはint型のデータを格納できる変数である。
- &bはbが格納されているアドレスを指す。
- bにはアドレスではなくデータが入っているのだから、*bは意味が無いし使えない。
C言語での表現に再挑戦
以上を踏まえて再度このソースを読もう。
#include<stdio.h> int main(void) { int *a; int b; a = &b; *a = 5; printf("%d\n",*a); printf("%p\n",a); printf("%p\n",&a); return 0; }
int *a; int b;
これは上で説明したから割愛する。
aはアドレスを、bは実数値を格納できるんだった。
a = &b;
aには*も&もついていないからaにはアドレスが格納できるんだった。
bには&がついているから、&bは変数bのアドレスを示すんだね。
つまりこれは
「変数bのアドレスをポインタaに格納しろ」ってことだ。
*a = 5;
こんどはaに*がついている。
ポインタに*がついているってことはポインタに格納されているアドレスに入ってるデータを示すんだった。
つまりこれは
「ポインタaが指すアドレスに格納されているデータに5を代入しろ」ってことか。
「ポインタaが指すアドレスに格納されているデータ」ってここではなんだっただろうか?
そう、bだったね。
つまりここでbの値に5が代入されたってことだ!
printf("%d\n",*a);
aに*がついている。
これはポインタに格納されているアドレスに入ってるデータを示すんだから
ポインタに格納されているアドレスはbのアドレス、
で、bに先ほどアドレス渡しで5を代入したんだから
ここでは「5」が表示されるね。
printf("%p\n",a);
aになにもついていない。
aはポインタ変数だから、アドレスが保存されているんだった。
上記でaには変数bのアドレスを格納したのだから
ここではそのまま変数bのアドレスが表示されるはず。
printf("%p\n",&a);
aに&がついている。
これはaそのもののアドレス(aが指すアドレスではない!)を示すんだった。
なのでここではaそのもののアドレスが表示されるね。
ちょっと分かりにくいひとは
パソコンのファイルイメージではaはエイリアスだったことを思い出して。
&aはエイリアスファイルそのもののありかを示すんだった。
ちなみに僕のデバッグ環境(Mac OS X 10.6.1, GNU gdb 6.3.50-20050815)では
以下のように表示された。
5(←ポインタaが格納しているアドレスに格納された値 = bの値) 0x7fff5fbff6ac(←ポインタaが格納しているアドレス = bのアドレス) 0x7fff5fbff6b0(←ポインタaそのもののアドレス)
いかがでしょう?
少しでも理解の助けになれば幸いです。