メモリリーク

「BINARY HACKS」に、メモリ関連のデバッグに使える「valgrind」の紹介があった。おもしろそうだったのでメモリリークの練習(ってなんや)をやってみた。まずはUbuntuで「aptitude install valgrind」とインストール。入れたのはvalgrind-3.3.0-Debianで、アーカイブは19.1MBと意外にでかい。

連結リストでmainの中に1つだけしかなかったList型のデータを、List *list[100]とやって、まず100本のリストを用意。MakeList()を呼んで明示的にメモリをアロケートするようにした。で、各リストを適当にデータで埋めて引き伸ばした後に、ClearList()でリストを消す。

リークのポイントはClearList()。まず最初に調べたのは、次のようなコード。

void ClearList(List *list) {
  Node *node, *target;
  node = list->head;
  while (node->next != NULL) {
    target = node;
    node = node->next;
    free(target);
  }
  InitList(list);
}

  :
  :

int main(void) {
  List *list[100];
  Data data;
  data.x = 320; data.y = 240;
  int s, i, loop = 100;

  while (--loop) {
    for (i = 0; i < 100; i++) {
      list[i] = MakeList();
      InitList(list[i]);
      for (s = 100; s < 200; s += 5) {
        data.size = s;
        InsertHead(list[i], &data);
      }
      //    PrintList(list[i]);
    }
    for (i = 0; i < 100; i++) {
      ClearList(list[i]);
      //    PrintList(list[i]);
    }
  }
}

そもそも*listを初期化しているだけで消していない。もともとリストを何本も作るようなことを考えていなかったので、list自体を消すことを想定していなかった。

で、これでは当然メモリに残る。gオプションをつけて、「gcc -g list.c」とコンパイルしたa.outに対して、

valgrind -v --error-limit=no --leak-check=yes --show-reachable=no ./a.out 2>&1 |less

とかやると、ちゃんと検出される。メッセージはかなり詳細で良く分からないところが多いけど、たぶんポイントは、

==7023== malloc/free: in use at exit: 237,600 bytes in 19,800 blocks.
==7023== malloc/free: 207,900 allocs, 188,100 frees, 3,247,200 bytes allocated.
==7023== 
==7023== searching for pointers to 19,800 not-freed blocks.
==7023== checked 60,356 bytes.
==7023== 
==7023== 79,192 bytes in 9,899 blocks are definitely lost in loss record 2 of 3
==7023==    at 0x4022AB8: malloc (vg_replace_malloc.c:207)
==7023==    by 0x8048425: MakeList (list.c:27)
==7023==    by 0x804879D: main (list.c:164)
==7023== 
==7023== 
==7023== 158,400 bytes in 9,900 blocks are definitely lost in loss record 3 of 3
==7023==    at 0x4022AB8: malloc (vg_replace_malloc.c:207)
==7023==    by 0x8048451: AllocNode (list.c:36)
==7023==    by 0x8048511: InsertHead (list.c:68)
==7023==    by 0x80487DE: main (list.c:168)
==7023== 
==7023== LEAK SUMMARY:
==7023==    definitely lost: 237,592 bytes in 19,799 blocks.
==7023==      possibly lost: 0 bytes in 0 blocks.
==7023==    still reachable: 8 bytes in 1 blocks.
==7023==         suppressed: 0 bytes in 0 blocks.

というあたり。exitしたときに1万9800ブロック、230KBほどのメモリが解放されていないことが分かる。ちゃんと内訳も出ていて、メモリの解放を忘れているメモリブロックについて、それを確保した場所が行数で表示されている。

今度はClearList()で、*listを初期化するのじゃなくて、ちゃんと解放するようにしてみる。

void ClearList(List *list) {
  Node *node, *target;
  node = list->head;
  while (node->next != NULL) {
    target = node;
    node = node->next;
    free(target);
  }
  free(list);
  //  InitList(list);
}

すると、valgrindのエラーは、次のようになる。

==7353== malloc/free: in use at exit: 158,400 bytes in 9,900 blocks.
==7353== malloc/free: 207,900 allocs, 198,000 frees, 3,247,200 bytes allocated.
==7353== 
==7353== searching for pointers to 9,900 not-freed blocks.
==7353== checked 60,348 bytes.
==7353== 
==7353== 158,400 bytes in 9,900 blocks are definitely lost in loss record 1 of 1
==7353==    at 0x4022AB8: malloc (vg_replace_malloc.c:207)
==7353==    by 0x8048451: AllocNode (list.c:36)
==7353==    by 0x8048511: InsertHead (list.c:68)
==7353==    by 0x80487DE: main (list.c:168)
==7353== 
==7353== LEAK SUMMARY:
==7353==    definitely lost: 158,400 bytes in 9,900 blocks.
==7353==      possibly lost: 0 bytes in 0 blocks.
==7353==    still reachable: 0 bytes in 0 blocks.

まだAllocNode()やInsertHead()で作ったオブジェクトが解放されていないことが分かる。このClearList()にはバグがあって、終端のNULLを検知したときにwhileから抜けてしまって最後のノードを1つ、消し損ねている。

なので、

void ClearList(List *list) {
  Node *node, *target;
  node = list->head;
  while (node->next != NULL) {
    target = node;
    node = node->next;
    free(target);
  }
  free(node);
  free(list);
  //  InitList(list);
}

とやるのが正しい。valgrindの結果は、

==7642== malloc/free: in use at exit: 0 bytes in 0 blocks.
==7642== malloc/free: 207,900 allocs, 207,900 frees, 3,247,200 bytes allocated.
==7642== 
==7642== All heap blocks were freed -- no leaks are possible.

となる。「no leaks are possible」と出てバンザイ。