_CrtSetDbgFlag でメモリリークを検出する手順:終了時に確保箇所を自動報告

【MFC】Memory Leak Detection: _CrtSetDbgFlag でリークを自動報告させる

newで確保したメモリをdeleteで解放し忘れると、長時間実行時のメモリ増加や不安定化につながります。

Cランタイム(CRT)のデバッグ機能を使うと、アプリの終了時に、解放し忘れたメモリが出力ウィンドウへ報告されます。ログには確保した場所も出るので、delete忘れの箇所までたどれます。


目次

_CrtSetDbgFlagによるリーク検出の有効化

アプリが起動した直後に、次のコードを一度だけ実行します。MFCならInitInstance()やOnInitDialog()など、最初に必ず通る初期化処理に入れます。リークを起こす処理より前に実行しておきたいので、起動直後が向いています。

#include <crtdbg.h> // ヘッダーのインクルードが必要

BOOL CMyApp::InitInstance()
{
    // === メモリリーク検出機構を有効化 ===
    // プログラム終了時に(main関数を抜けた瞬間に)自動的に
    // _CrtDumpMemoryLeaks() を呼び出すフラグを立てる
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    // ... 通常のアプリ初期化処理が続く ...
}

このフラグを立てると、アプリの終了時に、解放されていないメモリが残っていないかを調べます。残っていれば、Visual Studioの出力ウィンドウに報告が出ます。

出力ログの確認ポイント

わざとリークさせてアプリを終了すると、出力ウィンドウに次のようなログが出ます。

Visual Studioの出力ウィンドウにメモリリーク検出ログが表示されているスクリーンショット

Detected memory leaks! で始まり、その後に確保場所・サイズ・内容のヒントが続きます。

1. 確保した場所

c:\myproject\MyDlg.cpp(273)
メモリを確保した行です。出力ウィンドウでこの行をダブルクリックすると、その行へ移動できます。

ただし、ファイル名と行番号が出るのは、DEBUG_NEWが有効な.cppファイルで確保したメモリだけです。MFCウィザードが作った.cppファイルには、次の記述が最初から入っています。自分で追加したファイルには入っていません。

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

この記述がないファイルでnewを使うと、ファイル名と行番号は出ず、確保番号だけが表示されます。行番号が出ないときは、まずこの記述があるかを確認してください。

2. リークしたサイズ (確保サイズ)

100 bytes long
リークしたメモリの大きさです。1024 bytesなら画像バッファ、12 bytesなら小さな構造体、というように、何を確保したメモリかの見当が付きます。

3. データの中身 (Data)

Data: <Hello > 48 65 6C 6C 6F…
メモリの先頭に入っていた内容が、文字と16進数で表示されます。何に使っていたメモリかを推測する手がかりになります。


確保番号 (Allocation Number) で絞り込む

リーク報告に行番号(main.cpp(25) の部分)が出ず、次のように確保番号とサイズだけが出ることがあります。

{142} normal block at 0x00AABBCC, 100 bytes long.

この{142}は、プログラムの起動から数えて142番目に確保されたメモリという意味です。

実行の順序が毎回同じなら、この番号を指定して、確保の瞬間にデバッガを止められます。

// リーク報告で {142} と出たら、この番号で仕掛ける
_CrtSetBreakAlloc(142);

止まったら呼び出し履歴(Call Stack)を開き、自分が書いた関数までさかのぼって、確保した元を確認します。

ただし、MFCダイアログアプリのように内部でメモリ確保が頻繁に起きるプログラムでは、確保番号が実行ごとにずれやすい点に注意してください。UI操作の順序やタイミング、デバッガ自身の内部処理によって、同じコードでも番号が変わることがあります。

番号がずれて無関係な確保で止まったときは、最新のリーク報告から番号を取り直して、もう一度実行します。確保番号は目安にすぎず、毎回同じ場所で止まる保証はありません。


注意点と誤報(False Positive)について

  • このCRTのデバッグ機能が有効なのは、_DEBUGが定義されたDebugビルドのときだけです。Releaseビルドでは、これらのデバッグ用のマクロが無効になるため機能しません。
  • MFCのDebugビルドでは、フレームワーク自体が終了時にリークダンプを行う仕組みを備えているため、_CrtSetDbgFlagを明示的に呼ばなくてもリーク報告が出ることがあります。MFC以外のC++ライブラリを混ぜているときや、確実に報告させたいときは、この記事のとおり自分でフラグを立てておくと安心です。
  • 終了時の自動リークチェックは、C++のグローバルオブジェクト(関数外で定義されたクラス)のデストラクタが呼ばれる「前」に行われることがあります。そのため、実際にはアプリ終了直後にデストラクタで正常に解放されるのに、チェックのタイミングが早すぎてリーク扱い(誤報)される現象が起きます。報告が出てもすぐバグと断定せず、対象がグローバル変数やスタティック変数でないかを先に確認してください。

まとめ

  • [ ] アプリの起動直後に _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); を入れておく。
  • [ ] 終了時に出力ウィンドウのDetected memory leaks! を確認し、表示されたファイル名・行番号をダブルクリックしてdelete忘れを修正する。
  • [ ] 行番号が出ないリークは、確保番号({142} 等)をメモし、_CrtSetBreakAlloc(142); でその確保の瞬間に止める。ただしMFCアプリでは番号がずれやすいため、止まった先のCall Stackで自分のコードが見えるかを必ず確認する。

関連記事

目次