2012年01月08日

Visual C/C++ のメモリリークを追跡するには

Java や C# などの多くの言語は、ガベージコレクション機能によって、メモリリークは発生しないため、多くの開発者は心配しなくてよくなってきましたが、Google Chrome では Native Client 機能が追加されるなど、まだ C言語を使う機会があり、メモリリーク問題と戦わなければならないかもしれません。

メモリリークとは、malloc 関数や new 演算子を使って確保した変数(メモリ)を、free 関数や delete 演算子を使って開放することを忘れることです。 短時間で終了するプログラムなら、プログラムが終了するときに OS によってすべてを開放するために問題ないのですが、長時間稼動するプログラムでは、いずれメモリ不足になってシステムがダウンします。

Visual C++ では、プログラムの最後で、_CrtDumpMemoryLeaks 関数を呼び出すと、メモリリークを一覧することができます。 その一覧には、メモリ・ブロック(変数)の識別番号が含まれています。

  Dumping objects ->
  {738} normal block at 0x00A7EE60, 4096 bytes long.

上記の場合、メモリ・ブロックの識別番号は 738 です。 この番号を使って、main 関数の最初で _CrtSetBreakAlloc 関数を呼び出せば、そのメモリ・ブロックを確保した瞬間にブレークするようになります。

  _CrtSetBreakAlloc( 738 );

コールスタックから呼び出し元の関数を調べていけば、メモリリークを起こしている変数を発見することができます。 たとえば、コールスタックから下記のようなコードが見つかれば、var 変数の開放を忘れていることになります。

  var = malloc( 4096 );

識別番号は、メモリを確保した順番に付けられるため、プログラムが完全に同じ動きを再現できなければ、毎回プログラムを動かすたびに識別番号が変わってしまうため、変数を特定することはできません。

たとえば、WM_TIMER という Window メッセージに応答するプログラムを使って、クリックしたときに確保したメモリがメモリリークを発生させた場合、クリックするタイミングによって、識別番号が変わってしまいます。

この場合、クリックしたときの処理を開始したときから、相対的に識別番号を指定すれば、変数を特定することができます。

すべて Window メッセージに応答する CMainFrame::WindowProc 関数などに、下記のコードを埋め込んで実行すると、Window メッセージの種類と、メッセージを受信した瞬間のメモリ・ブロックの識別番号が printf されます。

{ // ここを通ってから、+0 番目にメモリ領域を確保したときにブレークさせる
long malloc_ID;
char* file_name;
int line_num;
void* heap_memory = malloc( 1 );
printf( "CMainFrame::WindowProc: (0x%04X)%s\n", message, WinX_getWMStr( message ) );
assert( _CrtIsMemoryBlock( heap_memory, 1, &malloc_ID, &file_name, &line_num ) );
printf( "_CrtIsMemoryBlock {%d} in %s(%d)\n", malloc_ID, __FILE__, __LINE__ );
if ( message == 0 ) _CrtSetBreakAlloc( malloc_ID + 0 );
free( heap_memory );
}

Window メッセージの種類と、メモリ・ブロックの識別番号の相対値を、
if ( message == 0 ) _CrtSetBreakAlloc( malloc_ID + 0 );
の 0 の部分(2箇所)に埋め込んで再実行すれば、メモリ・ブロックを確保した瞬間にブレークするようになります。

sage_p at 22:49│Comments(0)TrackBack(0) プログラミング 

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔