メモリリーク
「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」と出てバンザイ。