まずは、次の短いプログラムを見てみましょう。
int result, a = 2, b = 5;
result = a + b;
a
とb
の値を足して、result
に格納しています。その途中で、一時的にではありますが、result
に無意味な値が格納されているのが分かるでしょうか?
int result, a = 2, b = 5;
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;
これで、変数に不定な値が格納されることはなくなりました。
ただ、result
を0
で初期化したあと、その値を一度も使うことなくa + b
で上書きするというのは、少し無駄がありますね。気になるようなら、次のように手直しすればいいでしょう。
int a = 2;
int b = 5;
int result = a + b;
ポインタを初期化する
次の短いプログラムを見てみましょう。ポインタ経由で、整数の値を123
に書き換えています。
int *p, n = 0;
p = &n;
*p = 123;
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
に不定なアドレスが格納されるのを避けつつ、「メモリー内のどこも指していない」という意味をもたせています。
なお、p
を0
で初期化したあと、その値を一度も使うことなくn
のアドレスで上書きするのは少し無駄ですね。ここは、次のように手直ししてもいいでしょう。
int n = 0;
int *p = &n;
*p = 123;
使い終わったポインタをクリアする
次のプログラムでは、メモリーを一時的に確保して、そこに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;
}
int *p = malloc(sizeof(int));
if (p) {
*p = 123;
}
free(p);
p = 0;
free()
に0を渡してしまう気が……
malloc()
がメモリーの確保に失敗したときのことね。
p
が0になった場合です。
free()
には「引数が0の場合は何もしない」っていう仕様があるの。だから大丈夫なのよ。
NULL
)を代入してクリアしましょう。この基本ルールで、ダングリングポインタを防げます。