C言語とポインタ


C言語のポインタについて書かれた記事は多数あるが、 少し違った視点で書いてみたいと思う。

C言語で作ったプログラムの実行イメージと変数の配置について

変数配置イメージ

ポインタとは、変数やプログラム領域などのメモリ(アドレス)を指すものである。

わかりづらい点

ポインタアクセス

  • 配列の場合、配列名がポインタになる。int x[10] の場合、x が配列の先頭アドレスになる。&x[3] と x + 3 は同じ。
  • 変数、構造体の場合は、&変数名、&構造体名がポインタになる。
  • 配列を参照する時、x[5] でも、*(x+5)でも同じ。
  • 構造体のポインタの場合、->でメンバ変数にアクセスできる。

ポインタでの受け渡し

私自身が始めたころわかりづらいと思ったのは、戻り値が return 値ではなく、 引数にポインタとして入っていることがある点。例えば scanf。

複数のリターン値を返したい場合や、(新しいC言語仕様に対応していない場合だろうが)構造体を返したい場合には、リターン値を格納する領域へのポインタを渡して関数を呼び出し、呼び出し先で、その領域に結果を書き込んでリターンする、というのが典型的な使い方かと思う。例えば、int の値を返してもらう場合は、

int ret1;
int ret2;
hoge(&ret1, &ret2);

のようにしてポインタを渡す。

サブルーチン側では、

void hoge(int *ret1, int *ret2)
{
    *ret1 = 123;
    *ret2 = 345;
}

のようにして結果を書き込む。

一方で、

int ret;
hoge(ret);

として呼び出し、サブルーチン側で、

void hoge(int ret)
{
    ret = 100;
}

としても、呼び出し元の値は変わらない。

関数には値が渡される。前者は変数のアドレス(=ポインタ)という値が渡されており、後者は変数の値が渡されていることに注意する必要がある。

リターン値

ローカル変数を宣言し、そのポインタを返そうなどということをしてはいけない。

ローカル変数は、関数から戻った時には解放されているので、そのポインタが指す場所にはもうデータが無い。(たまたま残っていることはあるかもしれないが、それを期待してはいけない。)

動的なメモリ確保

すぐに、配列の大きさを固定値にできない場合が出てくることだろう。

そういった場合は、malloc を使う。しかし、malloc した領域は、free して解放しなければ、確保されっぱなしで、 プログラムが動作している間はどんどんメモリを消費することになる。

そのうち、メモリの限界に達してしまい、malloc に失敗し、プログラムが異常終了することになる。他のプログラムも巻き込んでしまうかもしれない。したがって、解放忘れをしないようにする必要がある。