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

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

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

この記事では、Cランタイム(CRT)のデバッグ機能を使って、終了時に解放漏れを報告させ、ログから確保箇所を確認する手順をまとめます。


目次

_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!」の文字と、ファイル名・行番号が表示されているスクリーンショット

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

一見すると読みにくいログですが、確認すべき情報は3つです。

1. 確保した場所

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

ただし、行番号が表示されるのは DEBUG_NEW マクロが有効な .cpp ファイルだけです。MFCウィザードが生成した .cpp ファイルには最初から以下の記述が入っていますが、自分で新しく追加したファイルには入っていません。

MFCウィザードが生成した .cpp には、デバッグ時に確保位置を追跡するための DEBUG_NEW 向け設定が最初から入っています。

この記述がないファイルで 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)について

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

まとめ

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