メモリリーク: 静かなる暗殺者

サーバーはこうして死ぬ。長期稼働システムの天敵。

メモリリーク
確保したメモリを解放し忘れること。メモリ残量が減っていき、最終的にシステムがクラッシュする。
OOM Killer
Linuxの機能。メモリ不足になったとき、メモリを食っているプロセスを強制終了させる暗殺者。
返却されなかったレンタカー (The Unreturned Rental)

メモリリークは「レンタカーを返却し忘れること」です。 車(メモリ)は手元にあるのですが、返却手続き(free)をしていないので、永遠に料金を取られ続けます。 1台だけならまだしも、これを何度も繰り返すと、世界中のレンタカーがなくなり(メモリ不足)、最終的にレンタカー会社がブチギレてあなたの契約を強制解除します(OOM Killerによる強制終了)。

返却忘れの末路

「メモリリーク」とは、malloc で借りたメモリを free で返し忘れることです。プログラム自体は正常に動き続けるため、テストでは見つけにくいのが厄介です。

しかし、稼働時間が長くなるにつれて少しずつメモリ使用量が増えていき、ある日突然、サーバーが応答しなくなったり、OSの「OOM Killer」によってプロセスが強制終了されたりします。

リークの仕組み

Memory Leak Code
void leak() {
// 1. 確保する
char *ptr = malloc(100);
// 2. 使う
// ...
// 3. 忘れる (freeしない!)
// 関数が終わると ptr 変数は消えるが、
// ヒープに確保された100バイトは誰にも返せずに残り続ける。
}
int main() {
while(1) leak(); // メモリを食いつくすまで止まらない
}

実践テクニック

goto cleanup イディオム

関数内で複数のリソース(ファイル、メモリ、ソケットなど)を確保する場合、エラーが起きるたびに個別に解放するのは大変ですし、バグの元です。

Safe Cleanup Pattern
// gotoを使ったクリーンアップ・パターン
// C言語のエラー処理の鉄板。Linuxカーネルでも多用される。
int process_file(const char *filename) {
FILE *fp = fopen(filename, "r");
if (!fp) return -1;
char *buf = malloc(1024);
if (!buf) {
// fpを閉じてから帰らないといけない
fclose(fp);
return -1;
}
// ... 処理 ...
if (error_happened) {
goto cleanup; // 共通の終了処理へ飛ぶ
}
// 正常終了
free(buf);
fclose(fp);
return 0;
cleanup:
// エラー時の後始末を一箇所にまとめる
if (buf) free(buf);
if (fp) fclose(fp);
return -1;
}

演習課題

課題1: 無限ループ
わざとメモリリークするループを書き、タスクマネージャを見ながらメモリ使用量が増えていくのを確認してください(確認したらすぐ停止すること)。

合格ライン

長時間稼働するシステムでリークが致命的な理由を知っている
エラーハンドリング時の解放漏れに注意できる