«前の日記(2008-03-23 (日)) 最新 次の日記(2008-04-02 (水))»  

まちゅダイアリー


2008-04-01 (火)

ポインタの話

何を書くかあんまり考えてないけど、C/C++のポインタの機能を読んで思ったことを書いてみるかな。 話題になってた元のソースはこれ。

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

このソースを Linux で動かすとコアダンプし、 Mac OS X だと Bus Error になった。 ちなみに -Wall をつけてコンパイルしたけど、ワーニングはでなかった。そういうものなのかな。

んで、今は以下のようなソースになってる。 (修正前の記事を読んだ人のために、訂正箇所を明示してほしかったなぁ…)

   #include <stdio.h>

    int main( void ) {
      int n = 5;
      int *p = &n;
      *p = 10;   /* ポインタ変数を使って値10を代入 */
      printf( "%d\n", n );  /* 変数nの値を出力(10が表示される) */
      return 0;
    }

ポインタを知っている人には、元のソースはポインタ変数 n が指す先が初期化されていないって分かるけど、知らない人には意外と間違えそうなところ。 ちょっと思いつくままに、いろいろと書いてみる。

一般論 … 初期化していない変数を参照しちゃダメ

これはポインタ変数に限らず一般的な話だけど、変数を使う場合はおおむね以下の手順になる。

  1. 変数を宣言する (値を入れる箱を用意する)
  2. 変数に値を代入する (箱の中に値を入れる)
  3. 変数の値を参照する (箱の中に入れた値を読む)

当たり前と言えば当たり前だけど、箱の中に値を入れる前は、箱の中に何が入っているか分からない。 だから、一度も値を代入していない変数の値を参照すると、結果は不定になる。

#include <stdio.h>
int main(void)
{  
    int a;
    printf("%d\n", a);

    return 0;
}

このソースも、 a に値を代入する前に値を参照しているので、何が出力されてもおかしくない。 Mac OS X だと 4096 が出力された。

ポインタの場合も同じで、初期化していないポインタ変数には、何が入っているか分からない。

#include <stdio.h>
int main(void)
{  
    int* p;
    printf("%p\n", p);

    return 0;
}

うちの Mac OS X だと、結果は 0x1000 と出力された。 この値に意味があるんじゃなくて、たまたまこの値が入っていただけ。

以下のように p を初期化すると、 p に意味のある値(この場合は n の値が格納されているメモリのアドレス)が入る。 このソースの実行結果は、 0xbffff178 となった。

    p = &n;
    printf("%p\n", p);

この 0xbffff178 という値は、 &n の値と同じ。 なので、以下の結果も 0xbffff178 となる。

    printf("%p\n", &n);                                                                                                                    

こんな感じで、ポインタ変数も普通の変数も、代入してから参照しないとダメ。

ポインタ変数 *n に5を代入?

ところで元のソースは

    int *n;
    *n = 5;

となっていて、一見すると「*n」という変数に5を代入しているようにみえる。 ここがポインタの落とし穴かつ難しいところかもしれない。

「*n」ってのは変数の名前じゃなくて、あくまで名前は n 。 n に対して *n という演算をして、その結果に 5 を代入している感じ(ちょっとここの説明は適当かも)。 擬似的に Java っぽく書くと、以下のような感じかな。Java には int* なんてないけど、まぁ勘弁。

   // int* 型の変数 n を宣言
   int* n;
   // n が参照している先のメモリ領域に 5 を書き込む
   n.getAddress().write(5);

これだと初期化していない n を参照しているってのが分かると思う。 *n が n.getAddress() 。 Java だと初期化していない参照型変数には null が入っていて、演算しようとすると NullPointerException が起きるところ。 でも C だとさっき見たように、 0x1000 などという不定な値が入っていて、その値が指す先のメモリ領域に 5 を書こうとする。 それでエラーがでる、と。

と、自分流に書いてみたけど、この説明じゃポインタが分かっている人じゃないと分からないよなぁ…。 以下、時間があれば書きたかったこと。

  • C になぜポインタがあるのか?(ポインタの必要性)
  • Java にはなぜポインタがないのか? (オブジェクトはすべて参照型変数で扱う, Integer などのラッパークラスの存在)
  • PHP の参照渡しと C のポインタ変数での値渡しの違い

追記

一晩明けて、 n.getAddress() は名前が悪いような気がしてきた。 &n が n.getAddress() だよね。 n.point().write(5) とか? うーん。いい名前が思いつかない。

それから、 &n は初期化しなくても使えるから、 new しないとメソッドが呼べない Java と比べるのは早くも破綻。

Tags: memo
本日のツッコミ(全5件) [ツッコミを入れる]
なかだ (2008-04-02 (水) 04:31)

$ gcc -O1 -Wall a.c<br>a.c: In function 'main':<br>a.c:5: warning: 'n' is used uninitialized in this function<br>初期化されているかの判断にはフロー解析が必要なので、-Oをつけないと警告は出ません。

すぎむら (2008-04-02 (水) 09:21)

getAddress() の代わりに getValue() はどうでしょう。

kenz (2008-04-09 (水) 03:51)

値をセットするからSetValueじゃないの?<br>int* n;<br>n.SetValue(5);<br><br>c#風だと<br>int* n;<br>n.Value = 5;<br>かな

kenz (2008-04-09 (水) 03:53)

追記で、修正されたソースも相変わらずバグってるような・・・<br>int *p = &n; って相変わらずpが初期化されてない・・・

kenz (2008-04-09 (水) 04:51)

あ、ごめんあってた<br>ポインタで変数nの値を書き換えるプログラムだったのね、 趣旨を理解してなかった