C言語の入門書を読み始めたばかりの初心者である学生が、先生に質問をしているようです。どんな内容かちょっと聞いてみましょう。
ポインタの使い方
学生: 先生、ポインタの宣言ってこれで合っていますよね。
int *p;
でもp[0] = 5;のように使うとエラーが出るんです。
先生: それは当たり前だ。まだメモリを確保していないだろう? メモリを確保しないと使えないぞ。
学生: でも配列の場合、int a[10];のように宣言したら使えましたよ。ポインタって全然ダメですね。
先生: ポインタと配列はまったく違うものだから、どちらがダメでどちらが良いと云うことはないよ。配列はその場でメモリを確保して使えるが(上記の場合はintを10個分)、プログラム内で決め打ちした数値になってしまう。つまり後から大きさの変更ができないんだ。
一方、ポインタは宣言しただけでは使えないが、後から自由にメモリを割り当ててその大きさを変えられるから、少ないメモリで済む場合は小さく、たくさんのメモリが必要な場合は大きくメモリを自由に確保することができる。
学生: へぇ。ところで、どうやってメモリを確保すればいいんですか?
先生: それはこのようにすればいい。
p = (int*)malloc(10 * sizeof(int));
これは、intのポインタにintの大きさで10個分のメモリを割り当てるという意味だ。確保したメモリが不要になったなら
free(p);
で開放する。この辺についてはその入門書にも載っているだろう。
学生: ふ~ん。なんだか難しそうですね。
先生: 慣れれば難しいことなどないよ。C言語ならmallocでメモリを割り当て、freeで開放と覚えていればいい。
学生: 分かりました。早速使ってみます。
ポインタと配列の違い
学生: ポインタを使ってみましたが、結局、中の数値を見たり変更したりする場合は、p[2]のように配列の形にしないといけないんですよね。なんだか混乱するなぁ。
先生: 別にp[2]ではなくても*(p+2)としても良いぞ。どちらもまったく同じ意味だ。配列型と同じように見えるからといって配列ではないからその点は気をつけることだな。因みに、*(p+2)は*(2+p)とも書けるので、2[p]とも書けることになり、実際にこれは文法上正しいのだ。
学生: えええ! そうだったんだ! 先生、これからは2[p]のように書くことにします!
先生: おいおい…、文法上正しくてもコーディングスタイルとしては正しくないぞ。文法も大事だが、それと共に分かりやすく書くことも大事なことなのだ。2[p]のような書き方はせいぜいコーディングの汚さを競うコンテスト、IOCCCぐらいでしか役に立たん。
学生: そうですかぁ…。じゃあ、使わないことにします。ところでさっき、先生はポインタと配列は違うようなことおっしゃってましたけど、配列からポインタに代入できますよね。それって結局同じことなんじゃないですか?
先生: それは違うぞ。例えばint a[10];という配列があるとして、int *p = a;とすることはできる。これはpに配列aの先頭の位置情報(アドレス)を入れるという意味だ。だからポインタpを使ってaの配列にアクセスできるようになる。
この場合、a[2]はaの配列の先頭から2つ先の数値という意味だが、p[2]はポインタpに入っている位置情報(aの配列の先頭位置)に2を加えた場所に入っている数値という意味になる。結局は同じ数値にたどり着くんだが、そこにたどり着くまでの経路が違うんだ。
学生: …ややこしいですね。だったら、char *p = "string";とchar a[] = "string";も違うってことですか? 同じように見えますけど…。
先生: もちろん違う意味だ。aでは配列にそれぞれ's', 't', 'r', 'i', 'n', 'g', '\0'の文字が入るわけだが、pではどこか遠くのメモリ領域(場合によっては書き換え不可)に"string"という文字列があって、その先頭の位置情報(アドレス)が入るわけだ。だからa[0] = 'a'として配列の内容を変更することは問題ないが、p[0] = 'p'とするとメモリ違反のエラーになることもある。
学生: やっぱり配列とポインタは違うんですねぇ。あれ? でも関数の仮引数、例えば、
void func(int a[]) { ...; }
では配列で取ったりしますよね。あれはどういうからくりなんですか?
先生: これは便宜上このような記法で書けるだけで意味はただのポインタだ。だから、仮引数のint a[]はint *aと読み替えれば間違いはない。
学生: なるほど、そうだったんですか~。ありがとうございました!
ポインタの使い方
学生: 先生、ポインタの宣言ってこれで合っていますよね。
int *p;
でもp[0] = 5;のように使うとエラーが出るんです。
先生: それは当たり前だ。まだメモリを確保していないだろう? メモリを確保しないと使えないぞ。
学生: でも配列の場合、int a[10];のように宣言したら使えましたよ。ポインタって全然ダメですね。
先生: ポインタと配列はまったく違うものだから、どちらがダメでどちらが良いと云うことはないよ。配列はその場でメモリを確保して使えるが(上記の場合はintを10個分)、プログラム内で決め打ちした数値になってしまう。つまり後から大きさの変更ができないんだ。
一方、ポインタは宣言しただけでは使えないが、後から自由にメモリを割り当ててその大きさを変えられるから、少ないメモリで済む場合は小さく、たくさんのメモリが必要な場合は大きくメモリを自由に確保することができる。
学生: へぇ。ところで、どうやってメモリを確保すればいいんですか?
先生: それはこのようにすればいい。
p = (int*)malloc(10 * sizeof(int));
これは、intのポインタにintの大きさで10個分のメモリを割り当てるという意味だ。確保したメモリが不要になったなら
free(p);
で開放する。この辺についてはその入門書にも載っているだろう。
学生: ふ~ん。なんだか難しそうですね。
先生: 慣れれば難しいことなどないよ。C言語ならmallocでメモリを割り当て、freeで開放と覚えていればいい。
学生: 分かりました。早速使ってみます。
ポインタと配列の違い
学生: ポインタを使ってみましたが、結局、中の数値を見たり変更したりする場合は、p[2]のように配列の形にしないといけないんですよね。なんだか混乱するなぁ。
先生: 別にp[2]ではなくても*(p+2)としても良いぞ。どちらもまったく同じ意味だ。配列型と同じように見えるからといって配列ではないからその点は気をつけることだな。因みに、*(p+2)は*(2+p)とも書けるので、2[p]とも書けることになり、実際にこれは文法上正しいのだ。
学生: えええ! そうだったんだ! 先生、これからは2[p]のように書くことにします!
先生: おいおい…、文法上正しくてもコーディングスタイルとしては正しくないぞ。文法も大事だが、それと共に分かりやすく書くことも大事なことなのだ。2[p]のような書き方はせいぜいコーディングの汚さを競うコンテスト、IOCCCぐらいでしか役に立たん。
学生: そうですかぁ…。じゃあ、使わないことにします。ところでさっき、先生はポインタと配列は違うようなことおっしゃってましたけど、配列からポインタに代入できますよね。それって結局同じことなんじゃないですか?
先生: それは違うぞ。例えばint a[10];という配列があるとして、int *p = a;とすることはできる。これはpに配列aの先頭の位置情報(アドレス)を入れるという意味だ。だからポインタpを使ってaの配列にアクセスできるようになる。
この場合、a[2]はaの配列の先頭から2つ先の数値という意味だが、p[2]はポインタpに入っている位置情報(aの配列の先頭位置)に2を加えた場所に入っている数値という意味になる。結局は同じ数値にたどり着くんだが、そこにたどり着くまでの経路が違うんだ。
学生: …ややこしいですね。だったら、char *p = "string";とchar a[] = "string";も違うってことですか? 同じように見えますけど…。
先生: もちろん違う意味だ。aでは配列にそれぞれ's', 't', 'r', 'i', 'n', 'g', '\0'の文字が入るわけだが、pではどこか遠くのメモリ領域(場合によっては書き換え不可)に"string"という文字列があって、その先頭の位置情報(アドレス)が入るわけだ。だからa[0] = 'a'として配列の内容を変更することは問題ないが、p[0] = 'p'とするとメモリ違反のエラーになることもある。
学生: やっぱり配列とポインタは違うんですねぇ。あれ? でも関数の仮引数、例えば、
void func(int a[]) { ...; }
では配列で取ったりしますよね。あれはどういうからくりなんですか?
先生: これは便宜上このような記法で書けるだけで意味はただのポインタだ。だから、仮引数のint a[]はint *aと読み替えれば間違いはない。
学生: なるほど、そうだったんですか~。ありがとうございました!
コメント