ポインタの初期化とクリア

モジュールごとに自分自身のヘッダーファイルをインクルードするっていうのを教わりましたけど……
うん。
これは定番の手法だっていう話でしたよね?
そうね。簡単にできて効果があるから、基本ルールといってもいいと思うわ。
基本ルールですか!えっと……そういうのって、ほかにもあったりします?
あー、そういうことね。いろいろあるわよー。
やっぱり!
それじゃあ、簡単で効果バツグンのやつを、いくつか紹介しちゃおうかしら。
わーい。ぜひぜひ!

まずは、次の短いプログラムを見てみましょう。

  int result, a = 2, b = 5;
  result = a + b;

abの値を足して、resultに格納しています。その途中で、一時的にではありますが、resultに無意味な値が格納されているのが分かるでしょうか?

ひとまず、1行で3つも変数を宣言しているところがよくないわね……
  int result, a = 2, b = 5;
変数宣言は、1つずつ行を分けるのがおすすめよ!
  int result;
  int a = 2;
  int b = 5;

このように行を分けて書くと、変数resultが最初に登場したとき、初期化されていないことがよく分かります。値が確定するのは、足し算の値を代入するときです。それまでの間、resultの値は不定(どのような値が格納されているのか分からない状態)になっているということです。

  int result; /* 初期化していないので、値が不定 */
  int a = 2;
  int b = 5;
  result = a + b; /* ここで、はじめて値が確定 */
ほんの短い間なのだけど、resultの値がどうなっているか分からない区間ができてしまっているわね。
それって、ダメなんですか?
ダメってことはないのよ。どこで値が不定になるのかを完璧に制御できるなら、とくに危険なことはないわ。
完璧に制御……それはちょっと無理かなぁ……
そうね。いつも完璧でいるのは難しいから、変数は初期化するっていうのを原則にするのがおすすめ。
反対に、初期化すると何かいいことがあったりするんですか?
セキュリティ上のリスクを減らせるはずよ。間違って不定な値をプログラムに読み込んでしまうことが少なくなるからね。
なるほどー。セキュリティのためですか。

では、変数resultが初期化されるようにプログラムを修正しましょう。といっても、次のように初期値(ここでは0)を書いておくだけです。

  int result = 0; /* 初期化 */
  int a = 2;
  int b = 5;
  result = a + b;

これで、変数に不定な値が格納されることはなくなりました。

ただ、result0で初期化したあと、その値を一度も使うことなくa + bで上書きするというのは、少し無駄がありますね。気になるようなら、次のように手直しすればいいでしょう。

  int a = 2;
  int b = 5;
  int result = a + b;
ここがポイント!
変数を初期化して、不定な値が格納されないようにしましょう。この基本ルールで、セキュリティ上のリスクを減らせます。

ポインタを初期化する

さてさて。C言語といえば、ポインタよね。
はい?
次は、ポインタの値が不定になってしまう場合について考えるわよ。
あー、そういうことですか!

次の短いプログラムを見てみましょう。ポインタ経由で、整数の値を123に書き換えています。

  int *p, n = 0;
  p = &n;
  *p = 123;
ひとまず、変数宣言が1行にまとまってしまっているから、行を分けて見やすくしましょうか。
ええと、この行だな……あれ?
  int *p, n = 0;
あのー、nはポインタですか?
この書き方だと、ちょっと混乱するわよね。pはポインタだけど、nは普通の整数型よ。
……と、いうことは、こう?
  int *p;
  int n = 0;
うん、正解!

これで、ポインタpが初期化されていないことが分かりました。pの値、つまりアドレスが不定なので、メモリー内のどこを指しているのかが分からない状態です。

  int *p; /* 初期化していないので、アドレスが不定 */
  int n = 0;
  p = &n; /* ここでアドレスが代入されて、nを指すことが確定 */
  *p = 123;

このような値が不定なポインタは、「ワイルドポインタ(wild pointer)」と呼ばれています。この例のpは、宣言されてからnのアドレスが代入されるまでの間が、ワイルドポインタになっています。

ワイルドポインタは、うっかりそのまま使ってしまうと危険です。プログラムのクラッシュや、セキュリティ上の問題を引き起こす恐れがあるためです。ポインタは必ず初期化して、不定なアドレスを指さないようにしましょう。

  int *p = 0; /* 初期化 */
  int n = 0;
  p = &n;
  *p = 123;

ここでは、0で初期化しました(NULLと書いても同じです)。これによりpに不定なアドレスが格納されるのを避けつつ、「メモリー内のどこも指していない」という意味をもたせています。

なお、p0で初期化したあと、その値を一度も使うことなくnのアドレスで上書きするのは少し無駄ですね。ここは、次のように手直ししてもいいでしょう。

  int n = 0;
  int *p = &n;
  *p = 123;
へぇー、ワイルドポインタっていうんですね。「野生のポインタ」ってことですか?
たぶんね。そう呼ばれてるってだけだから、正確な意味があるわけじゃないのだけど……「人が手を加えていない」から、どんな値(アドレス)が入っているか分からないっていうことね。
なるほどー。
ここがポイント!
ポインタは、宣言と同時に初期化しましょう。この基本ルールで、ワイルドポインタを防げます。

使い終わったポインタをクリアする

ワイルドポインタといえば、ぜひセットで覚えておきたい「ダングリングポインタ(dangling pointer)」というのもあるわね。
ダングリング……「宙ぶらりんのポインタ」ってことですか?
うん。さっきまで何かを指していたのに、どこを指しているのか分からなくなってしまったポインタのことよ。
んんー、ちょっとイメージわかないです……
じゃあ、例を見てみましょうか。

次のプログラムでは、メモリーを一時的に確保して、そこに123という値を格納しようとしています。でも、ポインタが「宙ぶらりん」になってしまっています。

  int *p = malloc(sizeof(int));
  free(p);
  *p = 123;

最初の行では、malloc()でメモリーを確保して、そのアドレスをポインタpに格納しています。このポインタを経由してメモリーに123を格納したいのですが、その前にfree()でメモリーを解放してしまっていますね。

解放されたメモリーは、もうプログラムの管理下にはありません。ところが、pにはまだ解放する前のアドレスが残っています。つまり、引き続き安全に使うことのできない、無効なメモリーを指してしまっている状態です。

もう有効なメモリーではないのに、そのアドレスだけが残ってしまったわけね。
なるほど。それが「宙ぶらりん」のイメージってことですか。
そういうこと!

ダングリングポインタは、メモリーが解放されたときに発生します。そして、メモリーが解放されると同時に使い終わったはずのポインタが、そのままの状態で残ってしまうことが問題です。

この問題を回避するには、使い終わったポインタを、もう使えない状態にすればいいですね。次のように、0(NULL)を代入してクリアしてしまいましょう。

  int *p = malloc(sizeof(int));
  free(p);
  p = 0; /* 使い終わったのでクリア */
  *p = 123;
えー、この修正はおかしくないですか?
言いたいことは分かるわ。これだとクラッシュするものね。
そうですよー。

上の例では、pをクリアしたあとに使おうとしているため、実行するとクラッシュするはずです。しかし、これはポインタをクリアしていること自体が間違っているわけではありません。もともと潜伏していたダングリングポインタのバグが、ポインタのクリアによって表面化したのです。

では、正しく動作するように修正しておきましょう。今回は、次の2点を修正すればいいでしょう。

  • malloc()を呼び出したあと、メモリーを確保できたかどうかチェックしていない
  • free()の呼び出しと、123を格納する処理の順序が逆
こんな感じに直せばいいわね!
  int *p = malloc(sizeof(int));
  if (p) {
    *p = 123;

    free(p);
    p = 0;
  }
または、これでもOK!
  int *p = malloc(sizeof(int));
  if (p) {
    *p = 123;
  }

  free(p);
  p = 0;
え、2つ目の直し方でもOKなんですか?なんか、free()に0を渡してしまう気が……
スルドイ!malloc()がメモリーの確保に失敗したときのことね。
えっと、そうですね……それで、pが0になった場合です。
実はね、free()には「引数が0の場合は何もしない」っていう仕様があるの。だから大丈夫なのよ。
そうなんですね!知りませんでしたー。
まぁ1つ目の直し方のほうが無駄がないし、プログラムとしても分かりやすいんじゃないかしら。
ここがポイント!
使い終わったポインタは、0(NULL)を代入してクリアしましょう。この基本ルールで、ダングリングポインタを防げます。