本当に怖いポインタの話

http://www.kt.rim.or.jp/%7ekbk/zakkicho/08/zakkicho0803c.html#D20080328-5 さん経由、C/C++のポインタの機能--変数の場所(アドレス) - builder by ZDNet Japan

コレは本当に酷すぎます。いきなりつかみからコレだもの。

ポインタ変数の宣言では、一般の変数の場合とは異なり名称の先頭に*がつけられる

「*n」 で変数名、* はプリフィクスという斬新な解釈?

続いて、*nを使って処理に用いる値を代入し、それを出力する例を示す。

(中略)

#include
 int main( void ) {
   int *n;
   *n = 5;   /* ポインタ変数nに値5を代入 */
   printf( "%d\n", *n );  /* ポインタ変数nが持つ値(5)の出力 */
   return 0;
 }

・・・・・・・・ぐはぁっ、突っ込みどころがありすぎよっ...。

1. 変数には複数の記述方法がある

先ほどまで*nと記述してきたポインタ変数は、宣言以降はnという記述許される。これが用いられるのは、たとえばキーボードなどから値を入力するときである。

・・・この人はイッタイナニヲイッテイルノダロウ。単に日本語が下手なのではなく、本当に「*n」という名前だと思ってるぽいです。*1なんだかどことなく Perl っぽいぞ、このC は。


* * *


絶対誰かが突っ込みエントリを作ってくれているとは思いますが、黙っていられなくなってしまいました。(お子ちゃま だなぁ、私)

えっと。

int *num_ptr;

は、"*num_ptr" という変数名じゃございませんことよ? int* num_ptr; と書いても int * num_ptr; と書いても一向に構いません。*2


C はキーワードをケチるのをモットーとしている言語なので、同じ記号やキーワードが 文脈によって違う意味になる、変な言語設計になっています。

変数宣言の中での * は、ポインタ型を表すキーワードです。「int * num_ptr」 で、num_ptr is pointer to int と読み下します。「intへのポインタ」型の変数 「num_ptr」を宣言です。(そのまんま "pointer" ってキーワードを作ってくれれば良かったのに、ねぇ?)

一方で C には、 間接参照を行う * という単項演算子があります。式中での * はコッチになります。これはオペランドにポインタをとり、ポインタが指す先をたぐり寄せます。


なのでこの例題のコード

int *n;
*n  = 5;

は、「未初期化のポインタn の指す先に 値5 をカキコする」という大変危険なコードになっています。うあぁぁ。


* * *


ポインタとはその名の通り「何かを指す(pointする)モノ」です。そしてポインタは型です。ポインタ型は通常「○○へのポインタ型」という形で特化されています。

ポインタは型なので、ポインタ型の変数もありますし、ポインタ型の値もあります。ポインタ型の値はしばしば「アドレス」と呼ばれます(←これが実はいくない)。これはポインタの実装の多くがアドレスになっているのと、もひとつ「ソレを指すポインタ値をgetする」演算子 & を C が堂々と「アドレス演算子」と呼んじゃってるせいです(^^;*3


C の ポインタはとてもプリミティブな機能なので、使い方次第でいろんな目的に使えます。リファレンスとして使ったり、イテレータとして使ったり。Cの多くの機能はこの「ポインタを○○に見立てて使う」でまかなわれていて、ソレを支援する文法も用意されているくらい 「C とは ポインタである」なのです。

しかし、この「見立て」を誤るといろいろ大変ですし、概念が混じっちゃってしょうがないので、C++ ではそれぞれ「見立て」ごとに別のモノへ分かたれて行くのですが、それについてはまた別のお話。


ポインタについては、配列とポインタの完全制覇 さんが解りやすいと思います。


* * *


しっかし、

このページをお読みいただいている読者にあっては、「C言語 ポインタ」「C++ ポインタ」などのキーワードでWeb検索をしたことがあるかもしれない。そうするとポインタに特化した内容の書籍をいくつか見つけられる。それだけ奥が深い機能ということである。

なんて仰るなら、そう言ったページや本を読んでちゃんと勉強してから記事を書いてくださいな。

追記

なんか後から見返せば、あたしの日本語もだめだめですね。

ポインタに関する混乱は、前橋さんが指摘するような、C言語の宣言と式中で 同じ記号が別の意味を持っていることも大きいのですが、もうひとつ私が思っていたのは、「ポインタ = ポインタ変数」という風に思われている混乱 もあるんじゃないかな、ということです。

つまり、

  • 「アドレスを格納する変数」が 「ポインタ」
  • int * という型宣言は、int が主役で * で修飾されている風なイメージ

見たいに捉えられていて、単なる「ポインタ型」というイメージが無いのでは。

ポインタ・・というか参照の概念と 変数の概念って似てるから、すーっと解け合ってしまうような所があって、それでも Cでは 「ポインタという type」とそれに対するオペレーションを用意することで、int とか そういうのと同じように扱ってしまうから、そこに躓くのかも知れないとか、思ってみたりするわけです。

だったら、ポインタ型の変数に ポインタ型の値が 入って、 &演算子オペランドに対する ポインタ値を取得する ・・・なんて (ちと乱暴だけれど)そんな説明の方が 実は解りやすいのではないか? なんて思ってみたり。

そんな思いつきを試してみたエントリでした。

*1:追記: この記事、実は一度修正を受けた後だったらしく、元々の記述には、 「一般の変数を一時的にポインタ変数に変えることもできる」 とか、 「このとき&nは、一般の変数nを一時的にポインタ変数に変えていることを表している。」 とか書いてあったそうな。うぅむ。

*2:なのですが、C は int * a b; と書いたとき、a がpointer to int、b が int になってしまうトンデモな言語なため、変数名のほうに * をくっつけて書く風習が定石になっています。

*3:この演算子の名前がアレゲなのはCプログラマにしばしば突っ込まれる難所ですが、個人的にはまぁ、連想配列を ハッシュと呼んでしまうくらいのかわいい命名ミスだと思いますにゃ。