メモリリーク

返し忘れると何が起きるか

前回、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 すること」と書いておく習慣が要ります。

数えれば見える

リークは目に見えないので、数えて見えるようにしますmallocfree をそのまま呼ぶ代わりに、回数を数えるだけの薄い包みを自分で用意します。

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_countfree_count を出し、差が0でなければリークしています。これは本物のツールがやっていることの、いちばん簡単な形です。実務では専用の検出ツールを使いますが、仕組みはこの数え上げと変わりません。差が出たときに「どこで確保したものか」まで知りたくなったら、包みの中で確保した場所を記録すればよい、という発想も同じ延長線上にあります。

今回はこの包みを自分で書いて、確保と解放の回数が一致するプログラムに仕上げます。

要件

  1. my_malloc は malloc を呼び、NULL でなかったときだけ alloc_count を1増やす
  2. my_free は NULL でなかったときだけ free_count を1増やしてから free を呼ぶ
  3. 1人ぶんずつ確保して読み、その場で返す
  4. 最後に合計・確保回数・解放回数・未解放件数を表示する

入出力例

main("3 80 90 70") → "合計 240確保 3解放 3未解放 0 件" main("4 55 65 75 85") → "合計 280確保 4解放 4未解放 0 件"

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.c
学習モード

メモ

メモリリーク

⌘S で保存