プログラムの実行経路上のメモリリーク検出ツールであるValgrindについて簡単にまとめました。
Table of Contents
1 Valgrindとは
プログラム実行経路上のメモリリークを検出するツールです。
mallocやnewを独自の関数に置き換え、メモリ領域の確保と同時に呼び出し情報を保持しておき、プログラム終了時に解放されていない呼び出し元を出力します。
2 使い方
以下のようなmallocされた領域がfreeされないコードを使います。
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { char *buffer = (char *) malloc(1024); if (buffer == NULL) return 1; buffer[0] = 1; /** free(buffer); */ return 0; }
コンパイル後、valgrindにかけてみます。
$ valgrind --leak-check=full ./memleak ==30429== Memcheck, a memory error detector ==30429== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==30429== Using Valgrind-3.11.0.SVN and LibVEX; rerun with -h for copyright info ==30429== Command: ./memleak ==30429== ==30429== ==30429== HEAP SUMMARY: ==30429== in use at exit: 36,305 bytes in 425 blocks ==30429== total heap usage: 505 allocs, 80 frees, 42,433 bytes allocated ==30429== ==30429== 1,024 bytes in 1 blocks are definitely lost in loss record 68 of 79 ==30429== at 0x100007351: malloc (vg_replace_malloc.c:303) ==30429== by 0x100000F44: main (memleak.c:6) ==30429== ==30429== LEAK SUMMARY: ==30429== definitely lost: 1,024 bytes in 1 blocks ==30429== indirectly lost: 0 bytes in 0 blocks ==30429== possibly lost: 0 bytes in 0 blocks ==30429== still reachable: 0 bytes in 0 blocks ==30429== suppressed: 35,281 bytes in 424 blocks ==30429== ==30429== For counts of detected and suppressed errors, rerun with: -v ==30429== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 15 from 15)
"by 0x100000F44: main (memleak.c:6)"と表示され、mallocされた領域がfreeされていない旨が検出されます。
3 メモリリークの種類
以下の様な単方向リストのノードを10回確保するサンプルプログラムmemleak.cを用いて、各メモリリークの違いを説明します。
#include <stdlib.h> #ifdef DEFINITELY_LOST # define FREE_TIME (9) #else # define FREE_TIME (10) #endif struct node { struct node *next; }; int main(void) { struct node *node = NULL; int i; char *sigsegv = NULL; for (i = 0; i < 10; i++) { struct node *prev = calloc(1, sizeof(*prev)); prev->next = node; node = prev; } #ifdef STILL_REACHABLE *sigsegv = 0; #endif /** STILL_REACHABLE */ #ifndef INDIRECTLY_LOST for (i = 0; i < FREE_TIME; i++) { struct node *next = node->next; free(node); node = next; } #endif /** INDIRECTLY_LOST */ return 0; }
3.1 definitely lost
変数に確保した領域がメモリリークしている場合に検出されます。
memleak.cでDEFINITELY_LOSTをdefineした場合に、変数nodeに最後のノードが代入されますが、解放されません。
$ gcc -DDEFINITELY_LOST -o memleak memleak.c $ $ valgrind --leak-check=full ./memleak $ ==46562== Memcheck, a memory error detector $ ==46562== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. $ ==46562== Using Valgrind-3.11.0.SVN and LibVEX; rerun with -h for copyright info $ ==46562== Command: ./memleak $ ==46562== $ --46562-- ./memleak: $ --46562-- dSYM directory is missing; consider using --dsymutil=yes $ ==46562== $ ==46562== HEAP SUMMARY: $ ==46562== in use at exit: 35,289 bytes in 425 blocks $ ==46562== total heap usage: 514 allocs, 89 frees, 41,489 bytes allocated $ ==46562== $ ==46562== 8 bytes in 1 blocks are definitely lost in loss record 2 of 79 $ ==46562== at 0x100007D27: calloc (vg_replace_malloc.c:630) $ ==46562== by 0x100000EF5: main (in ./memleak) $ ==46562== $ ==46562== LEAK SUMMARY: $ ==46562== definitely lost: 8 bytes in 1 blocks $ ==46562== indirectly lost: 0 bytes in 0 blocks $ ==46562== possibly lost: 0 bytes in 0 blocks $ ==46562== still reachable: 0 bytes in 0 blocks $ ==46562== suppressed: 35,281 bytes in 424 blocks $ ==46562== $ ==46562== For counts of detected and suppressed errors, rerun with: -v $ ==46562== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 15 from 15)
3.2 indirectly lost
構造体変数が持つメンバ変数のポインター経由でメモリリークしている場合に検出されます。
memleak.cでINDIRECTLY_LOSTをdefineした場合に、単一リスト全体が解放されません。
$ gcc -DINDIRECTLY_LOST -o memleak memleak.c $ valgrind --leak-check=full ./memleak ==46566== Memcheck, a memory error detector ==46566== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==46566== Using Valgrind-3.11.0.SVN and LibVEX; rerun with -h for copyright info ==46566== Command: ./memleak ==46566== --46566-- ./memleak: --46566-- dSYM directory is missing; consider using --dsymutil=yes ==46566== ==46566== HEAP SUMMARY: ==46566== in use at exit: 35,361 bytes in 434 blocks ==46566== total heap usage: 514 allocs, 80 frees, 41,489 bytes allocated ==46566== ==46566== 80 (8 direct, 72 indirect) bytes in 1 blocks are definitely lost in loss record 43 of 80 ==46566== at 0x100007D27: calloc (vg_replace_malloc.c:630) ==46566== by 0x100000F45: main (in ./memleak) ==46566== ==46566== LEAK SUMMARY: ==46566== definitely lost: 8 bytes in 1 blocks ==46566== indirectly lost: 72 bytes in 9 blocks ==46566== possibly lost: 0 bytes in 0 blocks ==46566== still reachable: 0 bytes in 0 blocks ==46566== suppressed: 35,281 bytes in 424 blocks ==46566== ==46566== For counts of detected and suppressed errors, rerun with: -v ==46566== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 15 from 15)
変数nodeに代入された領域は"definitely lost"となり、メンバ変数ポインタの領域は"indirectly lost"になります。
3.3 still reachable
確保された領域が解放されないままプログラムがabortした場合に検出されます。
memleak.cでSTILL_REACHABLEをdefineした場合に、単一リスト全体が解放されないままプログラムがabortします。
$ gcc -DSTILL_REACHABLE -o memleak memleak.c $ valgrind --leak-check=full ./memleak ==46597== Memcheck, a memory error detector ==46597== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==46597== Using Valgrind-3.11.0.SVN and LibVEX; rerun with -h for copyright info ==46597== Command: ./memleak ==46597== --46597-- ./memleak: --46597-- dSYM directory is missing; consider using --dsymutil=yes ==46597== Invalid write of size 1 ==46597== at 0x100000F21: main (in ./memleak) ==46597== Address 0x0 is not stack'd, malloc'd or (recently) free'd ==46597== ==46597== ==46597== Process terminating with default action of signal 11 (SIGSEGV) ==46597== Access not within mapped region at address 0x0 ==46597== at 0x100000F21: main (in ./memleak) ==46597== If you believe this happened as a result of a stack ==46597== overflow in your program's main thread (unlikely but ==46597== possible), you can try to increase the size of the ==46597== main thread stack using the --main-stacksize= flag. ==46597== The main thread stack size used in this run was 8388608. ==46597== ==46597== HEAP SUMMARY: ==46597== in use at exit: 35,361 bytes in 434 blocks ==46597== total heap usage: 514 allocs, 80 frees, 41,489 bytes allocated ==46597== ==46597== LEAK SUMMARY: ==46597== definitely lost: 0 bytes in 0 blocks ==46597== indirectly lost: 0 bytes in 0 blocks ==46597== possibly lost: 0 bytes in 0 blocks ==46597== still reachable: 80 bytes in 10 blocks ==46597== suppressed: 35,281 bytes in 424 blocks ==46597== Reachable blocks (those to which a pointer was found) are not shown. ==46597== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==46597== ==46597== For counts of detected and suppressed errors, rerun with: -v ==46597== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 15 from 15)
プログラムによってはabortした場合は解放をシステムに任せてしまうスタンスのものもあり、メモリリークとは扱いません。
サイズ削減を目指して、プログラムが正常終了する場合にもメモリ解放のコードを書かないプログラムも稀にありますが、個人的にはabort以外の経路は全てメモリ解放のコードを書くべきだと思います。
3.4 possibly lost
Valgrindが認識できない操作によって、追跡できなくなった領域がメモリリークしているかもしれない場合に検出されます。False Positiveな性質を持ちます。
どこまでValgrindが追跡できるかどうかは実装を詳しく見ないと分かりませんが、通常のポインタ操作ではまず出ないもののようです。
出る場合はメモリリークかどうかを調べ、メモリリークではない場合はコーディングを改善した方が良いかもしれません。
3.5 suppressed
suppressionファイルで検出を抑制された領域がある場合に出力されます。
suppressionファイルはsuppressionsオプションで指定できます。
supressionsオプションとは別に、/lib/valgrind/default.suppが読み込まれます
4 –suppressionsオプション
Valgrindで検出されるがメモリリークではない該当箇所に対し、suppressionファイルを–suppressionsで指定することで検出を抑制できます。
$ gcc -DDEFINITELY_LOST -o memleak memleak.c $ cat main.supp { <insert_a_suppression_name_here> Memcheck:Leak match-leak-kinds: definite fun:calloc fun:main } $ valgrind --suppressions=main.supp --leak-check=full ./memleak ==46683== Memcheck, a memory error detector ==46683== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==46683== Using Valgrind-3.11.0.SVN and LibVEX; rerun with -h for copyright info ==46683== Command: ./memleak ==46683== --46683-- ./memleak: --46683-- dSYM directory is missing; consider using --dsymutil=yes ==46683== ==46683== HEAP SUMMARY: ==46683== in use at exit: 35,289 bytes in 425 blocks ==46683== total heap usage: 514 allocs, 89 frees, 41,489 bytes allocated ==46683== ==46683== LEAK SUMMARY: ==46683== definitely lost: 0 bytes in 0 blocks ==46683== indirectly lost: 0 bytes in 0 blocks ==46683== possibly lost: 0 bytes in 0 blocks ==46683== still reachable: 0 bytes in 0 blocks ==46683== suppressed: 35,289 bytes in 425 blocks ==46683== ==46683== For counts of detected and suppressed errors, rerun with: -v ==46683== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 16 from 16)
4.1 –gen-suppressionsオプション
–gen-supressionsオプションを用いることで、メモリリーク検出時にsuppressionsファイルに記述内容も出力されます。
$ gcc -DDEFINITELY_LOST -o memleak memleak.c $ valgrind --gen-suppressions=all --leak-check=full ./memleak ==46695== Memcheck, a memory error detector ==46695== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==46695== Using Valgrind-3.11.0.SVN and LibVEX; rerun with -h for copyright info ==46695== Command: ./memleak ==46695== --46695-- ./memleak: --46695-- dSYM directory is missing; consider using --dsymutil=yes ==46695== ==46695== HEAP SUMMARY: ==46695== in use at exit: 35,289 bytes in 425 blocks ==46695== total heap usage: 514 allocs, 89 frees, 41,489 bytes allocated ==46695== ==46695== 8 bytes in 1 blocks are definitely lost in loss record 2 of 79 ==46695== at 0x100007D27: calloc (vg_replace_malloc.c:630) ==46695== by 0x100000EF5: main (in ./memleak) ==46695== { <insert_a_suppression_name_here> Memcheck:Leak match-leak-kinds: definite fun:calloc fun:main } ==46695== LEAK SUMMARY: ==46695== definitely lost: 8 bytes in 1 blocks ==46695== indirectly lost: 0 bytes in 0 blocks ==46695== possibly lost: 0 bytes in 0 blocks ==46695== still reachable: 0 bytes in 0 blocks ==46695== suppressed: 35,281 bytes in 424 blocks ==46695== ==46695== For counts of detected and suppressed errors, rerun with: -v ==46695== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 15 from 15)
メモリリークの内容確認をしてからですが、上記の出力をgrep -v "^–" | grep -v "^=="でパイプしてやれば、そのままsupressionファイルとして利用できます。
5 留意すべき点
Valgrindは実際に実行された経路上のメモリリークを検出するものです。
経路によっては検出できない場合もあります。
メモリリークを検出すべき実行経路を考慮する必要があります。
ライブラリを使用した最も単純なコードと–gen-suppressionsでsupressionファイルを作成しておき、自身のコードで使うと良いでしょう。
仮想環境の場合、グラフィックライブラリ周りでメモリリークが検出される場合があります。
これらは仮想環境の実装上の問題の場合があり、メモリリークではないかもしれません。
実環境の場合と比べてみると良いでしょう。
–gen-supressionsで検出すべきメモリリークを抑制しきれない場合はvalgrindは難しいかもしれません。
他のメモリリーク検出ツールを検討すべきです。
例えば、OSXでSDL2のプログラムにValgrindをかけた場合、多くの検出が出てしまいます。
–gen-suppressionsを使ってsuppressionファイルを作成して、–supressionsで読み込ませて再度Valgrindを掛けても新たな検出結果が出てしまう状況です。
代替え手段として、Linux上でSDL2のプログラムにValgrindを掛けるようにしています。