『楽しく!』『できる!!』C講座のページ


 今回はVC++を用いたメモリリークチェックについてまとめます。
new/delete や malloc/free のような動的にメモリをヒープ領域に確保する関数を使用するときに常につきまとうのがメモリリークの危険性です。
 確保したメモリの解放忘れはもちろん、プログラミングが不正処理で終了した場合にもメモリは解放されなくなります。
ここで取り上げるのは解放忘れなどによるメモリリークをチェックする方法についてです。

 まずはじめにメモリリークするプログラムの例を出しましょう。

 int main()
 {
   new int

   return 0;
 }

 極端すぎる例ではありますが、基本ですね。new によって確保したメモリーを delete によって解放していないプログラムです。  delete で解放する以前に new から返されるポインタすら確保していないので、解放なんて出来るわけありません。

 もう少し複雑な例を挙げるなら以下のようなモノもあるでしょう。

 int* GetNum(int n);

 int main()
 {
   int* pNum;
   pNum = GetNum(3);

   printf("Num=%d", *(pNum));

   return 0;
 }

 int* GetNum(int n)
 {
  int* p_num;
  p_num = new int;

  *(pNum) = n;

  return p_num;
 }

上の例は最初の例より少し複雑です。
 main関数の内部を見るだけではどこに問題があるのか分かりません。
ポインタを返す関数によってポインタを取得して、そのポインタを操作しているだけなのでなんら問題はありません。
 しかし、ポインタを返す関数では new によってヒープ領域にメモリを確保しているために解放する必要があります。
 しかしクライアントであるmain側ではその事実を知るすべはありません。
今回のような単純な関数とクライアントなら対して問題はありませんが、大きなプログラムの中で、なおかつ関数の制作者とクライアントの制作者が異なる場合…。このような設計をしているといずれはメモリリークの迷宮に陥ってしまいます。

 これらの問題に対する最善の対処はそのままnewを使うのではなく、スマートポインタなどを用いることですが、それはまた後日にしましょう。
 今回は既に製作してしまったプログラムで、知らず知らずのうちに発生してしまっているメモリリークをチェックする方法を述べます。


 メモリリークのチェックにはVCに用意されている関数を使います。

  _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

 この関数をプログラム中のどこかで呼び出すことで、プログラム中に起きたメモリリークについて報告されるようになります。リーク情報はVC++のアウトプットウィンドウに表示されます。ただし、デバッグモードで実行したときに限ります。

 この関数はヘッダファイル crtdbg.h に宣言されているため

  #include <crtdbg.h>

として読み込んでやる必要があります。


 そうするとアウトプットウィンドウにリーク情報が表示されます。
…が。


 が、しかし。
 このプログラムにはバグがあります。
 リーク位置が正しく表示されないのです。

アウトプットウィンドウ
Dumping objects ->
c:\program files\microsoft visual studio\vc98\include\crtdbg.h(552) :
{16} normal block at 0x00780EC0, 4 bytes long.
Data: < > CD CD CD CD
Object dump complete.

上のようにリーク位置が常に " crtdbg.h " の中だと表示されるのです。
これではリーク位置を辿れず、修正できないため、非常に不便です。
んなもんマイクロソフトがリークチェック用に提供しているプログラムの中で、メモリリークしてたらお話になりません(笑)

 このメモリリークチェックの機構は、new 演算子を new実行時にその情報を保存する形式のものにオーバーロードし、チェックするという仕組みなのですが、その new の定義がcrtdbg.h の中にインラインで宣言されているために起こっているようです。

 その為正しく表示させるためには、自分たちでさらにnew演算子を正しくオーバーロードしてやる必要があります。


  #include <cstdlib>
  #include <new>
  #include <memory>

  using namespace std;

  #include <crtdbg.h>
  // crtdbg.h をインクルードしたあとに _CRTDBG_MAP_ALLOC を
  //定義してやる
  #define _CRTDBG_MAP_ALLOC

  #define NEW ::new(_NORMAL_BLOCK, __FILE__, __LINE__)

  int main()
  {
   _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
   NEW int;

   return 0;
  }

 自分で定義したnew演算子はそれと分かるように大文字で NEW のようにすると良いでしょう。
 このようにしておけばアウトプットウィンドウに

アウトプットウィンドウ
Detected memory leaks!
Dumping objects ->
C:\MyProjects\MemleakTest\Test.cpp(17) :
{16} normal block at 0x00780EC0, 4 bytes long.
Data: < > CD CD CD CD
Object dump complete.

というようにメモリリーク情報が表示されます。
 今回はキチンと「Test.cpp(17)」=「Test.cppというファイルの(17)行目のnewで確保された変数がリークを起こしている」と表示されていることが確認できます。


 また今回の NEW のように自分で定義したキーワードをVC++で色を変えて表示させることができます。

 方法は簡単で、Msdev.exe(VC++本体)のおいてあるフォルダにUSERTYPE.DATというファイルを作ります。
このファイルの中に書かれた単語がユーザー定義キーワードとなり、VC++で指定することにより表示色を変更することができます。
各行に書くことの出来る単語は一つで、二つ以上書いた場合は一番頭の単語のみが登録されます。
 VC++での設定は「ツール」「オプション」「書式タブ」「ソースウィンドウ」「ユーザー定義キーワード」から色を変更すれば変更できます。
ただし、USERTYPE.DATファイルが読み込まれるのはVC++起動時のみのようなので新たなキーワードを追加したときはVC++の再起動が必要となります。


参考文献:

ローベルの部屋
第八報:メモリリークと crtdbg.h
http://www1.kcn.ne.jp/~robe/pf/pf008.html

メモリ リークの検出と特定
 http://www.microsoft.com/japan/msdn/vs_previous/visualc/techmat/feature/MemLeaks/default.htm

_CrtDumpMemoryLeaks
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/vclib/html/_crt__crtdumpmemoryleaks.asp