今回は gperftools(Google Performance Tools) を使ってメモリリークを検出してみたいと思う。

下のコードをコンパイルして試す。関数aと関数bでわざとメモリリークを発生させている。
#include <stdio.h> 
#include <stdlib.h> 
#include <sys/time.h> 

int a(void) {
	char* x = new char[4]; // memory leak
	x[0]='x';
	int i=0,g=0;
	while(i++<100000)
	{
		g+=i;
	}
	return g;
}
int b(void) {
	char* y = new char[4]; // memory leak
	y[0]='y';
	char* z = new char[4];
	z[0]='z';
	int i=0,g=0;
	while(i++<400000)
	{
		g+=i;
	}
	delete[] z;
	return g;
}

int main(int argc, char** argv)
{
	int iterations;

	if(argc != 2)
	{
		printf("Usage %s <No of Iterations>\n", argv[0]);
		exit(-1);
	}
	else
		iterations = atoi(argv[1]);

	printf("No of iterations = %d\n", iterations);

	struct timeval s, e;
	gettimeofday(&s, NULL);

	while(iterations--)
	{
		a();
		b();
	}

	gettimeofday(&e, NULL);
	printf("time = %lf\n", (e.tv_sec - s.tv_sec) + (e.tv_usec - s.tv_usec)*1.0E-6);
	return 0;
}

Makefile は以下のとおり。リンクフラグに -ltcmalloc を追加している。
# Makefile
program = gpttest
objs = gpttest.o

CC = g++
CFLAGS = -g -Wall

LD = g++
LDFLAGS = -ltcmalloc

.SUFFIXES: .c .o

$(program): $(objs)
	$(LD) -o $(program) $(LDFLAGS) $^

.c.o:
	$(CC) $(CFLAGS) -c $<

.PHONY: clean
clean:
	$(RM) $(program) $(objs)

make が済んだら、以下のようにして実行する。1行目は libtcmalloc.so.4 がないと怒られるため、パスを通している。そして3行目の環境変数 HEAPCHECK に値をセットすることで、実行時にメモリリークのチェックが行われるようになる。
> export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
> export PPROF_PATH=/usr/local/bin/pprof
> export HEAPCHECK=strict
> ./gpttest 3

実行後、以下のような表示が行われ、メモリリークが発生していることがわかる。
[user1@localhost gperftools-test]$ sh gpttest.sh
WARNING: Perftools heap leak checker is active -- Performance may suffer
No of iterations = 3
time = 0.002770
Leak check _main_ detected leaks of 24 bytes in 6 objects
The 2 largest leaks:
Using local file ./gpttest.
Leak of 12 bytes in 3 objects allocated from:
	@ 4007f6 a
	@ 40091a main
	@ 309721ea4d __libc_start_main
Leak of 12 bytes in 3 objects allocated from:
	@ 400840 b
	@ 40091f main
	@ 309721ea4d __libc_start_main


If the preceding stack traces are not enough to find the leaks, try running THIS shell command:

pprof ./gpttest "/tmp/gpttest.4870._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv

If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks more repea
Exiting with error code (instead of crashing) because of whole-program memory leaks

中ほどにpprof で始まる行があり、このコマンドを実行すればgvでメモリリークの発生場所を表示してくれる。のだが、gv は入っていないため text で表示してみる。最後の --gv を --text に変える。
> pprof ./gpttest "/tmp/gpttest.4870._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --text

実行結果は以下のとおり。メモリリークの原因となったファイルと行番号を教えてくれる。
Using local file ./gpttest.
Using local file /tmp/gpttest.4870._main_-end.heap.
Total: 6 objects
       3  50.0%  50.0%        3  50.0% a /home/yamashita/gperftools-test/gpttest.c:6
       3  50.0% 100.0%        3  50.0% b /home/yamashita/gperftools-test/gpttest.c:16
       0   0.0% 100.0%        6 100.0% __libc_start_main ??:0
       0   0.0% 100.0%        3  50.0% main /home/yamashita/gperftools-test/gpttest.c:48
       0   0.0% 100.0%        3  50.0% main /home/yamashita/gperftools-test/gpttest.c:49

graphviz を使うことでグラフィカルに表示することもできる。 まずはgraphviz のインストール。
> yum install graphviz

続いて gperftool の結果を処理する。gvのところをdotに変える。
> pprof ./gpttest "/tmp/gpttest.4870._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --dot > gpttest.dot
> dot -T png gpttest.dot > gpttest.png

出力された画像が次だ。
gpttest

以上。