2015年10月11日

ポインタの呪い

明日は体育の日で休みですね。

C言語を使う上で避けることのできない(使わなくてもなんとかなるかもしれませんが)概念に「ポインタ」があります。

int i = 3;
int* ip = &i;

と書くと、ip は i へのポインタ ( i を指し示すもの ) となって、

int j = *ip;

と書けば j に ip が指し示す i の値が代入され、

*ip = 6;

と書けば ip が指し示す i に 6 が代入されます。

理解すれば非常に便利なポインタですが、これのせいで C 言語のマスターをあきらめたという人も多いようです。たいていは、変数がメモリ上に保持される様子を使って説明されていたりして、特に初心者にとってはわかりづらいのではないのでしょうか。また、文法そのものもとっつきにくい原因となっていると思います。関数からポインタそのものを受け取りたいような場合は

void f( size_t sz, char** cpp )
{
  *cpp = malloc( sz );
}

という具合に '**' で「ポインタのポインタ(ハンドル)」を表します。さらに関数ポインタの配列などは型の書き方がややこしくてたいていは忘れてしまい、本を見て思い出すといった具合です。C++ では参照が使えるようになって、ポインタはあまり意識しなくてもいいようになりました。関数ポインタも、関数オブジェクトというさらに便利な機能によってほとんど利用せずに済みます。
さらに Java や C# は「オブジェクトは参照渡しで組み込み変数は値渡し」と決められているので、完全にポインタのようなややこしい部分は意識しなくてよくなったわけですが、「参照渡しだから」ということで関数内で新しいインスタンスを構築して渡そうとしてうまくいかないといった失敗例をよく見かけます。

void f( SomeClass sc )
{
  sc = new sc( ... );
}

この場合、C 言語でいうところの "普通の" ポインタ渡しを意味するので、オブジェクトのある位置が変数として渡されるだけです。その変数に新たなインスタンスの位置を代入しても、元のインスタンスは影響を受けないので意味がないわけです。ちなみに C# の場合はハンドルを渡すために ref キーワードがありますね。

void f( ref SomeClass sc )
{
  sc = new sc( ... );
}

とすれば、意図したとおり新たなインスタンスで書き換えてくれます。

もし、このあたりで悩んでいる方がいれば、参考になれば幸いです。