Valgrindの使い方

プログラムの実行経路上のメモリリーク検出ツールであるValgrindについて簡単にまとめました。


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を掛けるようにしています。