はじめに
C言語で動的にメモリを確保する場合、確保したメモリが不要になった段階でfreeする必要があります。これを怠るとリソース不足になる恐れがあります(現代だとほとんど問題にならないそうですが、学習する段階では少なくともこのように教わることが多いと思います)。
本記事ではメモリリーク防止の考え方を自分なりに整理しようと思います。
メモリリークの検証方法
メモリリークを検知するために使用するツールとしては複数選択肢があります。筆者はファイルディスクリプタなどの検知もしやすいvalgrindが好きで、今回もvalgrindを使用します。
valgrind等動的解析ツール
静的解析ツール
メモリリークを防止するコードの考え方
守りたいルールは以下の3つだけです。
- malloc系を呼び出した関数内でfreeする
- 1が叶わない場合、malloc系を呼び出した関数を呼び出した先でfreeする
- 1が叶わない場合、malloc系を呼び出した関数を呼び出した先以外でfreeしない
※ここでルールの2つ目について補足です。これに当てはまる場合はおそらく返り値に動的に確保したメモリを使用しているためという前提があります。
※3つ目については、これに違反するとデバッグが難しくなってしまいます。
これを踏まえて、以下の例を見てみましょう。
悪い例
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char *get_str(const char *msg)
{
return (strdup(msg));
}
int main(void)
{
char *str;
str = NULL;
str = get_str("hello");
printf("%s\n", str);
return (0);
}
こちらをコンパイルしてvalgrindを使用すると以下が得られます。
※一部マスクや省略をしています。
$ valgrind --leak-check=full ./a.out
==84483== HEAP SUMMARY:
==84483== in use at exit: 6 bytes in 1 blocks
==84483== total heap usage: 2 allocs, 1 frees, 1,030 bytes allocated
==84483==
==84483== 6 bytes in 1 blocks are definitely lost in loss record 1 of 1
==84483== at 0x4848899: malloc (in /usr/~)
==84483== by 0x491758E: strdup (strdup.c:42)
==84483== by 0x401154: get_str (in /home/~)
==84483== by 0x401185: main (in /home/~)
==84483==
==84483== LEAK SUMMARY:
==84483== definitely lost: 6 bytes in 1 blocks
==84483== indirectly lost: 0 bytes in 0 blocks
==84483== possibly lost: 0 bytes in 0 blocks
==84483== still reachable: 0 bytes in 0 blocks
==84483== suppressed: 0 bytes in 0 blocks
==84483==
==84483== For lists of detected and suppressed errors, rerun with: -s
==84483== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
strdup内にあるmallocしたメモリが開放されていないことを検知しました。
先程のルールに乗っ取って考えると
- malloc系を呼び出した関数内でfreeする
→get_str関数の返り値に使用しているため、これはできないケース - 1が叶わない場合、malloc系を呼び出した関数を呼び出した先でfreeする
この2番を実行する必要があります。
良い例
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char *get_str(const char *msg)
{
return (strdup(msg));
}
int main(void)
{
char *str;
str = NULL;
str = get_str("hello");
printf("%s\n", str);
// 呼び出し先でfree追加する
free(str);
return (0);
}
$ valgrind --leak-check=full ./a.out
==85652== HEAP SUMMARY:
==85652== in use at exit: 0 bytes in 0 blocks
==85652== total heap usage: 2 allocs, 2 frees, 1,030 bytes allocated
==85652==
==85652== All heap blocks were freed -- no leaks are possible
==85652==
==85652== For lists of detected and suppressed errors, rerun with: -s
==85652== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
解決しました。
終わりに
今回はルールを3つだけ設けて、メモリリークを事前に防ぐようなコードを書ける考え方を提案しました。なにかのお役に立てれば幸いです。お読みいただきありがとうございました。
友人から以下のリンクを提示いただきました。次のレベルアップに良いそうです。筆者も勉強します。