VCメモリリーク検出ツール

VC++でメモリリークチェックツールはいくつかありますが私的に手放せない一品をご紹介。

Visual Leak Detectorです。インストールはOk連打でOk↓

install

途中Visual Studioとの連携を取るか確認されますのでもちろんチェックしておきます。

setup

使い方は至極簡単。どこかのcppファイル内で#include vld.h”を足すだけ。

 Test.cpp

#include "stdafx.h"
#include "vld.h"

int _tmain(int argc, _TCHAR* argv[])
{
	int* p = new int[256];
	return 0;
}

デバッグモードのみ自動的にライブラリがリンクされ、プログラム終了後出力ウィンドウに下のような出力がでます。

< 出力ウィンドウ>

Visual Leak Detector Version 2.4RC2 installed.
WARNING: Visual Leak Detector detected memory leaks!
---------- Block 1 at 0x006A31A0: 1024 bytes ----------
  Leak Hash: 0x2741707D, Count: 1, Total 1024 bytes
  Call Stack (TID 6308):
    0x0F27C260 (File and line number not available): MSVCR120D.dll!operator new
    c:\work\memoryleakchecksample\memoryleakchecksample\memoryleakchecksample.cpp (9): MemoryLeakCheckSample.exe!wmain + 0xA bytes
    f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c (623): MemoryLeakCheckSample.exe!__tmainCRTStartup + 0x19 bytes
    f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c (466): MemoryLeakCheckSample.exe!wmainCRTStartup
    0x7708338A (File and line number not available): kernel32.dll!BaseThreadInitThunk + 0x12 bytes
    0x77979F72 (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x63 bytes
    0x77979F45 (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x36 bytes
  Data:
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........


Visual Leak Detector detected 1 memory leak (1060 bytes).
Largest number used: 1060 bytes.
Total allocations: 1060 bytes.
Visual Leak Detector is now exiting.
プログラム '[6304] MemoryLeakCheckSample.exe' はコード 0 (0x0) で終了しました。

素晴らしい、そして美しい。

広告

C++/CLIで覚えておくべきこと

C#からCの関数をコールする場合、おもに2通りの方法があります。

  • CをDLLとして公開し、C#からP/Invokeを使う
  • CをDLLorLibとして公開し、C++/CLIで.NETラッパを作成しC#から.NET呼出し

後者の場合、C++/CLIについていくつか知っておくべきことがありますので簡単にご紹介。

 

1.デストラクタとファイナライザ

C++/CLIの・・・はC#の・・・に対応しており、、などとややこしい記述がたくさんあるのでここでは要点だけをまとめておきます。。C++/CLIのデストラクタ・ファイナライザについて覚えておくべきことは以下3点。

  • C++/CLIのデストラクタ、ファイナライザどちらもケースによって呼ばれないことがある
  • そのため、アンマネージリソースを扱うクラスを定義する場合、デストラクタからファイナライザをコールし、ファイナライザ内でアンマネージリソースを解放する
  • またアンマネージリソースを扱うクラスを使う場合、クラスを明示的にdeleteする。(C#からそのクラスを使う場合disposeする)

以上。C#からC++/CLIを参照することはあっても逆参照はないと思いますので上記内容があれば十分かと。上記2点目を実際に定義する場合、以下のようになります。

PatternSearch.cpp

#include "stdafx.h"
#include "PatternSearch.h"

namespace OpenCvWrap{

	// -----------------------------------
	// --  public func implementation   --
	// -----------------------------------
	PatternSearch::PatternSearch(System::Drawing::Bitmap^ srcImage)
	{
		// IplImage*
		cvTemplate = Converter::CreateIplImage(srcImage);
	}
	// デストラクタ
	PatternSearch::~PatternSearch()
	{
		this->!PatternSearch();
	}
	// ファイナライザ
	PatternSearch::!PatternSearch()
	{
		if (cvTemplate != NULL){
			pin_ptr<IplImage*> p = &cvTemplate;
			cvReleaseImage(p);
			cvTemplate = NULL;
		}
	}
}

補足ですが上のクラス内cvTemplateはOpenCVのIplImageへのポインタです。このようにC++/CLIにポインタをメンバ変数として持った場合、そのアドレスを&を使って取得しようとすると

‘cli::interior_ptr<Type>’から’IplImage **’に変換できません。

というエラーが出ます。GCによってアドレスが変わることを防いでいるためですね。そのため上のようにpin_ptrを使ってアドレスを固定して呼び出しをしてやる必要があります。

 

2.デザイナが出力するファイル

C++/CLIでクラスを新規作成したときにできる.hファイルはこんな感じです。

Converter.h

#pragma once
ref class Converter
{
public:
	Converter();
};

せめて.NETのクラス定義なんだから名前空間とクラスのアクセス修飾子は明示しましょう。

#pragma once
namespace OpenCvWrap{

	public ref class Converter
	{
	public:
		Converter();
	};
}

あとは.hファイルにはむやみにusing namespace System::Drawing;など書かないこと。C++/CLIのクラス間では.hファイルのインクルードなので名前がかぶってしまう可能性があります。

 

3.Nativeな構造体は持てない

C++/CLIのクラスにCで定義した構造体をメンバーに持つことはできません。保持できるのはWORD, SHORTなどのプリミティブ型と構造体へのポインタだけです。コンストラクタでnewしてやるなど適宜フリーストアからメモリを確保しましょう。

 

4.IntPtrとBYTE*の変換例

例えばSystem::Drawing::BitmapクラスとOpenCVのIplImageとの変換処理を作るとき、24BitのフルカラーBmpからモノクロ8bitへは以下のようなstatic関数で変換が可能です。

 Converter.cpp

namespace OpenCvWrap{

	IplImage* Converter::BitmapToIplImage(System::Drawing::Bitmap^ src){
		IplImage* dst = cvCreateImage(cvSize(src->Width, src->Height), IPL_DEPTH_8U, 1);
		int dstStride = dst->widthStep;
		System::Drawing::Rectangle^ srcRect = gcnew System::Drawing::Rectangle(Point::Empty, src->Size);
		BitmapData^ bmpData = src->LockBits(*srcRect,
			ImageLockMode::ReadWrite, PixelFormat::Format24bppRgb);
		void* pvSrc = (void*)bmpData->Scan0;
		BYTE* pbySrc = static_cast<BYTE*>(pvSrc);
		int srcStride = bmpData->Stride;

		for (int y = 0; y < dst->height; y++){
			for (int x = 0; x < dst->Width; x++){
				dst->imageData[y * dstStride + x] =
					(BYTE)(
						( 118 * pbySrc[y * srcStride + 3 * x + 0] +
						  600 * pbySrc[y * srcStride + 3 * x + 1] +
						  306 * pbySrc[y * srcStride + 3 * x + 2] ) / 1024
					);
			}
		}
		src->UnlockBits(bmpData);
		return dst;
	}
}

ここでbmpData->Scan0を一旦void*で受けて、その後static_cast<BYTE*>としていますがこれはIntPtrからBYTE*への直接変換ができないためです。これをしようとすると、

‘static_cst’ : ‘System::IntPtr’ から ‘BYTE *’に変換できません。

と言われてしまいます。

だいたいこれくらい知ってれば困らないんじゃないかなーと。