About

地図会社で働いていたプログラマ。

2012年12月8日土曜日

VCでメモリリーク箇所をログに出力する方法


以下のブログにあるようにMSVC CRTのメモリリーク検知機能でVisual Studioのデバッグ出力ウィンドウにリーク箇所を出力可能。このメッセージをログファイルに出力する方法を調べたのでメモ。

備忘録 - [Windows] CRT のメモリリークデバッグ -
http://limejuicer.blog66.fc2.com/blog-entry-14.html

// STLやBoost等ヘッダはnewの置き換えによってコンパイルエラーが出ることが
// あるので、先にインクルードしておく
#include <vector>

// メモリリーク検知のための必要なヘッダ、定義
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#define new ::new(_NORMAL_BLOCK, __FILE__, __LINE__)

int main()
{
    // プログラム終了時に自動でメモリリーク箇所を出力する設定
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    // メモリリーク
    int *val = new int[3];
    val[0] = 0;
    val[1] = 10;

    return 0;
}

するとデバッグ出力ウィンドウこんな感じで、リーク箇所のソース名、行数、ダンプを出力可能。


次にVCのCRTにはこのデバッグ検知時の処理をフックするイベントハンドラを登録する_CrtSetReportHookW2というAPIがあり、このコールバック関数でログファイルに出力できる。まず、_CrtSetReportHookW2とそのイベントハンドラのシグニチャは以下のとおり。
// 第一引数modeには、_CRT_RPTHOOK_INSTALLか,_CRT_RPTHOOK_REMOVEを指定
// 第二引数には、イベントハンドラへのポインタを指定する
int _CrtSetReportHook2(int mode, _CRT_REPORT_HOOK myHookFunction);

// イベントハンドラのプロトタイプ
int myHookFunction(int reportType, char *message, int *returnValue)

こんな感じでコンソールやログファイルに出力できる。
// STLやBoost等ヘッダはnewの置き換えによってコンパイルエラーが出ることが
// あるので、先にインクルードしておく
#include <vector>
#include <iostream>
#include <Windows.h>
#include <cstdio>

// メモリリーク検知のための必要なヘッダ、定義
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#define new ::new(_NORMAL_BLOCK, __FILE__, __LINE__)

/// <summary> CRTメモリ検知イベントハンドラ </summary>
/// <param name="reportType"> 処理タイプ </param>
/// <param name="message"> CRTからの出力メッセージ </param>
/// <param name="returnValue">  </param>
/// <returns> TRUE: _CrtSetReportHookを呼び出さない, FALSE: 呼び出す  </returns>
int __cdecl myHookFunction(int reportType, char *message, int *returnValue)
{
    switch (reportType)
    {
    case _CRT_ASSERT:
    {
        break;
    }
    case _CRT_WARN:
    {
        // メモリリークの場合は。ここで、自分のログ出力関数でファイル出力すれば良い。
        // このサンプルではコンソールに出力する。
        printf("%s\n", message);
    break;
    }
    case _CRT_ERROR:
    {
        // assert等が呼ばれた場合
        break;
    }
    default:
    {
        break;
    }
    // FALSEを返すと_CrtSetReportHookで登録したハンドラを呼び出す。
    return FALSE;
}

int main()
{
    // プログラム終了時に自動でメモリリーク箇所を出力する設定
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    // 第一引数には、_CRT_RPTHOOK_INSTALL(ハンドラ追加)か_CRT_RPTHOOK_REMOVE(ハンドラ削除)を指定
    // 第二引数には、イベントハンドラを指定する
    _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, myHookFunction);

    // メモリリーク発生
    int *val = new int[3];
    val[0] = 0;
    val[1] = 10;

    return 0;
}

出力結果
c:\memoryleak1\memoryleak1.cpp(61) :{70}
normal block at 0x00575E90, 12 bytes long.

Data: <            > 00 00 00 00 0A 00 00 00 CD CD CD CD

Object dump complete.

0 Kommentarer:

コメントを投稿