wakatonoの戯れメモ

はてなダイアリーから引っ越してきました。

ポインタって参照渡しのような機能なのか?(汗)

from Yendot
コメントに「これはひどい…」とあったので、見てみるが…確かにこれはひどい
。タイトルは勢いあまってる程度とするにしても、解説の内容がとにかくひどい。つうか、サンプル動かねえw
C/C++のポインタの機能--参照渡しのような処理って、タイトルもさることながら、1ページ目から突っ込みどころ満載である。
ちょっと見てみよう。


ポインタ変数の宣言
 ポインタ変数の宣言では、一般の変数の場合とは異なり、名称の先頭に*がつけられる。以下はnというポインタ変数を宣言する例である。ここでは、*nが表す値のデータ型はintとなるが、それ以外のデータ型でポインタ変数を宣言すれば、*nはその型の値を表すことになる。

int *n; /* int型の値を表すポインタ変数nの宣言 */
int n; /* 一般の変数 n を宣言する場合 */

ここまではよい。単純に宣言の書き方と、その宣言が何を意味するか?ということを書いてあるからだ。
問題はその次からである。


 続いて、*nを使って処理に用いる値を代入し、それを出力する例を示す。このときnには代入された値を記憶した場所(アドレス)が自動的に代入されるため、nそのものの値は実行されるごとに異なる可能性があるものの、*nが表す値は、処理中に変更されない限り5のままである。

#include

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

えーと…どういえばいいのだろうか?
おそらく、#includeの後に何もないのは、<と>で囲まれた部分がタグであると解釈され、消し飛ばされたくらいに考えてるので、そこはご愛嬌(実際、HTMLのソースを見てみたら、<stdio.h>と書かれていた)。
問題は次である。

int *n;という宣言で実施されるのは、アドレスを格納するための変数(これをポインタ変数という)が宣言されているだけである。しかも単に宣言されただけである。
で、その次におもむろに *n = 5; とやっているが、これは「宣言されたnというポインタ変数で指し示される領域に、5という値(整数値)を入れる」という意味である。これは明らかにおかしい、というかまともに動くわけがない、と直感的に感じる。

オレが知らないうちにCの仕様が拡張されたか?とか間抜けなことを思いつつ、一応当該コードをコピペしてコンパイル(#include のあたりは普通に修正して)、起動すると、案の定以下のような感じに。


wakatono@kid:~/cnet$ !cc
cc sample.c
wakatono@kid:~/cnet$ ./a.out
Segmentation fault
wakatono@kid:~/cnet$

あたりまえである。
単に「アドレス入れる変数を宣言」して、確保された領域の先頭も何も教えずに、そこにデータを突っ込もうとしてるんだから、Segmentation faultでけられて当然である*1
あまり筋がいいとは思わないが、たとえばこのプログラムは以下のような形で修正すれば、「とりあえずは」動くようになる。


int main( void )
{
int *n;
n = malloc(sizeof(int));
printf("%x\n",n);
*n = 5;
printf( "%d\n", *n );
return 0;
}
ポインタ変数nを宣言し(int *n;)、nに対して、「malloc()で確保したintの大きさだけの領域の先頭アドレス」を渡し(n = malloc(sizeof(int));)、その後でそこの領域に5という値を入れる(*n = 5;)というふうにすれば、つじつまが合う。

2ページ目についても同様の間違いがある。


int main( void ) {
int *n;
scanf( "%d", n ); /* *nの値をキーボードなどから入力(Enterで終了) */
printf( "%d\n", *n ); /* nの値を出力 */
return 0;
}

これもまた、同様の理由で動かない*2
なんでこんな間違いをするのだろうか?と思ったが、次の文章にその答えが書いてあった。


 プログラム内で用いる値は*nでを示しているのだが、nが示しているのは、その値を記憶させるメモリ上のアドレスだ。それ自体は自動的に設定されるため、開発者が具体的なアドレスを設定する必要はない。

???
これはおかしい。
初期化がすんでいない変数の値は不定である。そして、その領域が不幸にして使えない場合、結果はエラーとなる。使える場合には、値の代入が行われるわけだが、この結果は不定である。
実際、同じバイナリを別のLinuxカーネルなマシンの上で動作させたら、そちらでは動作してたりする。

あと、その次のプログラムはきちんと動く。


int main( void ) {
int n;
scanf( "%d", &n ); /* nの値をキーボードなどから入力(Enterで終了) */
printf( "%d\n", n ); /* nの値を出力 */
return 0;
}

これは、「すでに領域が確保された変数nのアドレス」を、scanf()で指定しているから、普通に動く。

以下は持論だが、ポインタを使うプログラムを書く場合には、ポインタを使うだけの理由が普通にあるのだから、どんな種類の値や、どういう関数/システムコールで取得した値が入るべきか、というのは気をつけるべき。でないと、思わぬバグを作りこむ結果になる。

そして、この記事で示されたプログラムだが、2ページ目の最後のもの以外は「実行結果が不定」という恐ろしいものになっている。
これが解説記事であるというのはちょっと認められない…。

2008/04/01追記:1ページ目が修正されました。

*1:動くほうが不思議だ

*2:正確には、動く保障がない