ヒープチェッカーでメモリリークを検出する
google-perftoolsには、TCMallocの一部としてC++プログラムのメモリリークを検出するヒープチェッカーが付属している。これを用いることでプログラム中でメモリリークが発生している場所を容易に特定することができる。ただし、FreeBSDではまだこの機能は利用できない。ここではCentOS 5での実行例を紹介する。
まず、プログラム全体に対するメモリリークをチェックしたい場合には、main()メソッドの最後の辺りにHeapLeakChecker::NoGlobalLeaks()メソッドの呼び出しを追加しておく。このメソッドは
以下にヒープチェック行うプログラムの例を示す。この例のLeakHidden()メソッドは意図的にメモリリークが発生するようにしてある。
leaksample.cc
#include <google/heap-checker.h>
static void LeakHidden() {
char* p = new char[1];
delete p;
new char[1];
// この時点で1byte分のメモリリークが発生
}
int main(int argc, char** argv) {
LeakHidden();
// メモリリークをチェック
HeapLeakChecker::NoGlobalLeaks();
}
ヒープチェッカを使用するためには、コンパイル時に-ltcmallocオプションを指定してTCMallocのライブラリをリンクする必要がある。例えばlibtcmalloc.soが/usr/local/lib/ディレクトリに、google/heap-checker.hが/usr/local/include/ディレクトリにインストールされている場合には以下のようにする。または、TCMallocの場合と同様に実行時にLD_PRELOAD環境変数にlibtcmalloc.soを追加する方法も可能だ。
-ltcmallocオプションを指定してleaksample.ccをコンパイル
$ gcc -o leaksample -L/usr/local/lib/ -ltcmalloc -I/usr/local/include/ leaksample.cc
実行時にはHEAPCHECK環境変数を指定する。この環境変数には以下の6つの値のうちのどれかを指定することができ、これによってチェックの厳密さが決まる。なお、各モードについての詳細は公式サイトのドキュメントを参照して欲しい。
- minimal: 初期化ルーチンのみを調べる最小限のチェックモード
- normal: 参照不能オブジェクトの有無などの基本的なメモリリークをチェックするモード
- strict: normalに近いが、より厳密なチェックを行うモード
- draconian: 確保した全てのメモリが開放されているかをチェックする。最も厳密なチェックを行うモード
- as-is: 環境変数により各種オプションを指定できるモード
- local: 後述する方法でプログラムの一部分のみチェックする場合に使用するモード
以下は、先程のleaksample.ccをnormalモードでチェックした例である。
ヒープチェッカーをnoemalモードで使用して実行した例
$ HEAPCHECK=normal ./leaksample
WARNING: Heap leak checker is active -- Performance may suffer
No leaks found for check "_main_" (but no 100% guarantee that there aren't any): found 17 reachable heap objects of 403 bytes
Heap memory leaks of 1 bytes and/or 1 allocations detected by check "_main_".
TO INVESTIGATE leaks RUN e.g. THIS shell command:
pprof ./leaksample "/tmp/leaksample.25322._main_-end.heap" --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --gv
....以下省略....
よく見ると1箇所のメモリリークが発生していることがわかる。そして詳細を調べるためにpprofコマンドを実行するよう指示している。pprofはgoogle-perftoolsに付属するプロファイル情報を可視化するためのツールである。指示された通りのコマンドを実行すると、gvが起動して以下のようにPostscript形式のコールグラフが表示される。
今回はLeakHiddenメソッドで1回のメモリリークが発生していることがわかる。ちなみに、ノードの大きさがリークの頻度の多さ(割合)を表している。
もしプログラム内の特定の部分に対してのみメモリリークのチェックを行いたいときにはHeapLeakCheckerオブジェクトを利用する。この場合、オブジェクトが生成された場所から、SameHeap()メソッドかNoLeaks()メソッドが呼び出されるまでの間のみチェックが行われる。例えば次のように利用する。
プログラムの一部分のみチェックを行う
HeapLeakChecker heap_checker("test_foo");
// この間のみメモリリークのチェックが行われる
if (!heap_checker.SameHeap()) assert(NULL == "heap memory leak");