2012年7月20日金曜日

メモリリークを検出する

要点
#include <crtdbg.h>
とインクルードして、main()関数の頭とかに

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
を書いておくと、メモリリークがあった場合、アプリケーション終了時にデバッグウインドウにレポートが出力されます。


ーーー経緯ーーーーー
_CrtDumpMemoryLeaks()という関数を使うとデバッグ実行中にnewで確保したメモリをdeleteし忘れていないかチェックする事が出来ます。

例)
#ifdef _DEBUG
   #ifndef DBG_NEW
      #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
      #define new DBG_NEW
   #endif
#endif  // _DEBUG

int main()
{
   int *p = new int;
   int *p2 = new int[10];

 //現時点で開放していないメモリをデバッグ窓に出力
 _CrtDumpMemoryLeaks();
}

写真をクリックすると拡大します。

int *pは4バイト、
int *p2は40バイト消費している事が分かります。


必要なヘッダ
#include <stdio.h>
#include <crtdbg.h> //必須

解説

写真17行目の

_CrtDumpMemoryLeaks();
によって、プログラムの現時点でnewしたのに開放していないメモリをデバッグ窓に出力します。
これでどのアドレスの何バイトのメモリが未解放かが分かるのですが、アドレス渡されても意味不明だし、バイト数だけのヒントも心もとないのでmain()関数の前に書いたマクロによって、そのメモリを何処のファイルの何行目で確保したかという追加の情報が出力される様にしています。
このマクロは、newで確保する以前に書いておく必要があります。

一般的な使い方としてはアプリケーション終了前に
_CrtDumpMemoryLeaks();
を呼んでメモリリークをチェックします。

でも、出力ウインドウなんて毎回チェックしないかも知れないのでアサートと組み合わせてすぐに分かるようにした方が良いでしょう。

_ASSERT(!_CrtDumpMemoryLeaks());

(_CrtDumpMemoryLeaks()は、メモリリークが見つかった場合にTRUE(=1)を返します。)

ただし、アプリケーション終了時に_CrtDumpMemoryLeaks()を書くと言っても、newを使わないグローバル領域のオブジェクトのデストラクタがその時点で呼ばれていないのは当然なので、そのオブジェクトの内部でnewで確保した領域があるとそれがメモリリークとして検出されてしまうという問題が発生します。

・・・という分けで今まで書いたことは忘れた方が良いかも知れません。

いくつか試しましたが結局、アプリケーションの開始時に
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
を書いとくという方法が無難だと感じました。

プログラミングはあなた自身の冒険の旅です。
もっと良い方法はあなた自身が探しだして下さい・・・。
ドラクエを超えるのはドラクエだけ(なんのこっちゃ)

参考にしたMSDNの情報
_CrtDumpMemoryLeaks
CRTライブラリを使用したメモリリークの検出

0 件のコメント: