導入
malloc/free を手で対応させるルールは単純ですが、破ると3つの典型的なバグが起こります。メモリリーク(free忘れ)、ダングリングポインタ(解放済みの場所を使う)、ダブルフリー(同じ場所を2回解放する)です。それぞれ「悪い例」と「直した例」を見比べて、なぜ危険か・どう防ぐかを押さえましょう。
説明
まずはメモリリークです。確保したのに free を忘れると、そのメモリは二度と使えないまま残ってしまいます(プログラムが終了すればOSが回収しますが、長時間動くプログラムでは確保のたびに使えるメモリが減っていきます)。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int)); // 確保
*p = 42;
printf("%d\n", *p);
// free(p); を書き忘れると、このメモリは二度と解放されない → メモリリーク
return 0;
}
直した例は、使い終わったら必ず free を呼ぶだけです。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int));
*p = 42;
printf("%d\n", *p);
free(p); // 使い終わったら必ず解放する
return 0;
}
次はダングリングポインタです。free した後もポインタ自体(住所を覚えた変数)は残っているので、うっかりそのまま使ってしまうことがあります。解放後の領域は「もう自分のものではない」ため、何が入っているかは保証されません。この例は危険なので実際には実行しないでください。
// ❌ 危険な例(実行しない)
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int));
*p = 10;
free(p); // ここで p の指す場所は「解放済み」になる
printf("%d\n", *p); // 解放済みの場所を読んでいる(ダングリングポインタ)。結果は保証されない
return 0;
}
直し方は「解放したら、その場でポインタに NULL を入れておく」「使う前に NULL チェックする」の2つです。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int));
*p = 10;
free(p);
p = NULL; // 解放したらすぐ NULL にしておく
if (p != NULL) { // 使う前に必ずチェックする
printf("%d\n", *p);
} else {
printf("解放済み\n");
}
return 0;
}
最後はダブルフリーです。同じポインタを2回 free すると、内部の管理情報が壊れ、以降の malloc/free がおかしくなることがあります。この例も危険なので実際には実行しないでください。
// ❌ 危険な例(実行しない)
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int));
*p = 5;
free(p);
free(p); // 同じポインタをもう一度 free している(ダブルフリー)
return 0;
}
ここでも「解放したら NULL を入れる」が効きます。free(NULL) は「何もしない」と規格で決められた安全な呼び出しなので、誤って2回目を呼んでも壊れません。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof(int));
*p = 5;
free(p);
p = NULL;
free(p); // NULL の free は安全(何もしない)
printf("解放済み\n");
return 0;
}
flowchart TB a["malloc"] --> b["使う"] b -->|"free を忘れる"| leak["メモリリーク"] b -->|"free した後もpを使う"| dang["ダングリングポインタ"] b -->|"free を2回呼ぶ"| dbl["ダブルフリー"] b -->|"free の後 p = NULL"| safe["安全(3つとも防げる)"]
やってみよう
上の「直した例」(リーク対策・ダングリング対策・ダブルフリー対策)を実行して、いずれも問題なく動くことを確かめましょう。「危険な例」とコメントされたコードは、コードを読んで理解するだけにして、実際には書き換えて実行しないでください。
演習
int *p = malloc(sizeof(int)); で確保し、*p = 100; を入れて printf("%d\n", *p); で表示したあと、free(p); で解放し、続けて printf("解放済み\n"); を表示してください(p を使い回さない、正しい後始末の形です)。
ヒント1を見る
printf → free(p); → printf("解放済み\n"); の順に書きます。
ヒント2を見る
free の後に p を読み書きするコードを書かないのが正解です。