メモリリーク
返し忘れると何が起きるか
前回、malloc で借りて free で返す形を書きました。返し忘れたものをメモリリークと呼びます。
リークしても、その場では何も起きません。エラーも警告も出ず、テストも通ります。困るのは長く動かしたときです。借りたまま返さない領域が少しずつ積み上がり、使えるメモリが減っていきます。1回の実行が一瞬で終わるプログラムなら終了時にOSがまとめて回収してくれますが、常駐するサーバーや、電源を切らない組込み機器では、数日後に確保が失敗し始めます。原因の行から遠く離れた場所で落ちるので、リークは見つけにくいバグの代表です。
リークが起きる典型の形
意図して忘れる人はいません。次の3つの形で自然に生まれます。
途中で return してしまう。
int *p = malloc(sizeof(int) * n);
if (n > 100) {
return 1; /* p を返さないまま抜けている */
}同じ変数を上書きする。
int *p = malloc(sizeof(int) * 3);
p = malloc(sizeof(int) * 5); /* 最初の領域のアドレスが消えた */アドレスを失った領域は、もう free に渡すことができません。借りた場所への唯一の手がかりを捨てているので、取り返しがつきません。
確保する場所と解放する場所が離れている。
関数の中で確保して、呼び出し側に返す設計にすると、返す責任が呼び出し側に移ります。この受け渡しがあいまいなまま関数が増えると、誰も free を書かない状態になります。関数のコメントに「戻り値は呼び出し側が free すること」と書いておく習慣が要ります。
数えれば見える
リークは目に見えないので、数えて見えるようにします。malloc と free をそのまま呼ぶ代わりに、回数を数えるだけの薄い包みを自分で用意します。
int alloc_count = 0;
int free_count = 0;
void *my_malloc(size_t size) {
void *p = malloc(size);
if (p != NULL) {
alloc_count++;
}
return p;
}プログラムの最後で alloc_count と free_count を出し、差が0でなければリークしています。これは本物のツールがやっていることの、いちばん簡単な形です。実務では専用の検出ツールを使いますが、仕組みはこの数え上げと変わりません。差が出たときに「どこで確保したものか」まで知りたくなったら、包みの中で確保した場所を記録すればよい、という発想も同じ延長線上にあります。
今回はこの包みを自分で書いて、確保と解放の回数が一致するプログラムに仕上げます。
要件
- my_malloc は malloc を呼び、NULL でなかったときだけ alloc_count を1増やす
- my_free は NULL でなかったときだけ free_count を1増やしてから free を呼ぶ
- 1人ぶんずつ確保して読み、その場で返す
- 最後に合計・確保回数・解放回数・未解放件数を表示する
入出力例
main("3
80 90 70") → "合計 240点
確保 3 回
解放 3 回
未解放 0 件"
main("4
55 65 75 85") → "合計 280点
確保 4 回
解放 4 回
未解放 0 件"