純規の暇人趣味ブログ

手を突っ込んで足を洗う

[VC++]crtdbgで#define newする時の悩み事

      2016/08/25    HimaJyun

そこそこにこそこそとDirectX9の勉強をしています、DirectX9EXだとデバイスロストが発生しづらい(フォーカスを失ってもデバイスロストしない)とか言うのが良さげですね。

さて、メモリリーク調査でcrtdbgを使った時に#define newみたいにして行番号が出る様にしているのですが、icludeしたヘッダに「operator new」があると上手く動かないので、対策方法をいくつか考えてみました。

有能で無能なcrtdbg

前回の記事でちらっと触れましたが、以下の様にする事でメモリリークの発生している行番号がデバッガに表示されます。

#define WIN32_LEAN_AND_MEAN
 
#include <Windows.h>
 
#ifdef _DEBUG // メモリリーク発見君
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#include <stdlib.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif // _DEBUG
 
//=======================
// よくある普通のWinMain
//=======================
int WINAPI WinMain(_In_ HINSTANCE hInstance,
                   _In_opt_ HINSTANCE hPrevInstance,
                   _In_ LPSTR lpCmdLine,
                   _In_ int nCmdShow) {
 
    #ifdef _DEBUG
    // メモリリーク検出君呼び出し
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    #endif
 
    return(S_OK);
}

ここで重要となるのは以下の行番号を表示させるための細工です。(細工しないとメモリリーク検出は出来るが行番号が出ない)

#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)

ただ、この細工には以下の様な問題がありまして……

無能ポイント

さて、私はDirectX9を勉強していましてね、本をコピペ改造ぶっ壊しつつ色々やってみているのですが、今回は以下の様なコードを書きました。

#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#include <d3dx9.h>

説明のために必要なので「#define new」書いていますが、実際はこれはstdafx.hにあります。

話の流れ的にお察しでしょうが、「型指定子が必要です」、「配列を返す関数は使用できません」、「関数を返す関数は使用できません」なるエラーが出ました。

何が厄介って、このエラーが自分のコードではなく、「d3dx9math.h」から出ている事です。

エラーが出ている個所には以下の様な記載がありました。

void* operator new ( size_t );
void* operator new[] ( size_t );

先ほどのマクロを展開すればこうなる訳です。

void* operator new(_NORMAL_BLOCK, __FILE__, __LINE__) ( size_t );
void* operator new(_NORMAL_BLOCK, __FILE__, __LINE__)[] ( size_t );

そりゃ、まぁ、動く訳ないよな……

と、言う訳で、この問題を解決するいくつかの方法を考えてみました。

解決方法

とは言え、あまり綺麗な解決方法は無いみたいで、どれも一長一短です。

とりあえず思いついた方法をいくつかご紹介しましょう。

正攻法、new以外の文字を使う

今から新しい物を作るのであれば(もしくは、newを置換出来る規模ならば)漏れなくこちらをオススメ

「#define new」を以下の様にしましょう。

#ifdef _DEBUG
#define NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#else
#define NEW new
#endif

その後、newを使うべき所で代わりに「NEW」を使用します。

こうする事で、外部ヘッダ(と言ったら良いのかな?)の中で使用されている「new」には反応せず、「NEW」を使用した場合のみマクロ展開が行われます。

メリットとしては、紹介する中でも比較的綺麗な解決方法である事、stdafx.hなどで1度書いてしまえば後は何も要らない事

デメリットとしては、newではなくNEWを使わなければならない事

#undef newからの再#define

それが出来ない(newが多すぎて)、NEW使うの忘れそう、なんか気持ち悪い、とかなら、エラーの出た場所で以下の様にします。

#undef new
#include <d3dx9.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)

要は#undefで一旦マクロを消して、#includeで読み込んでから、もう一度#defineでマクロを再定義する訳ですね。

少々気持ち悪い手法ですが、エラーの出るヘッダファイルが少ないのであれば楽で有効な方法でしょう。

メリットとしては、普通のnewでOKな事(読みやすい)、stdafx.hなどで1度書いてしまえば(エラーが出ない限り)後は何も要らない事、局所的に無効に出来る事

デメリットとしては、精神的に気持ち悪い事、面倒な事、エラーが出たらそこは全て#undef->#defineしなければならない事

逆の考え方、先に#includeする。

#undef戦法が気に食わない、かつ、エラーの出る#includeが少ないのであれば以下の様な手法もありでしょう。

例えばメモリリーク検出用のコードをstdafx.hに書いているのであれば以下の様にするのです。

#include <エラーの出るヘッダ>
#include <d3dx9.h>

#ifdef _DEBUG // メモリリーク発見君
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#include <stdlib.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif // _DEBUG

すなわち、エラーの出るヘッダを先に#includeしておくのです。

メリットとしては、普通のnewでOKな事(読みやすい)、stdafx.hなどで1度書いてしまえば後は何も要らない事

デメリットとしては、やはり気持ち悪い事、自分のコードの中で「operator new」を使えなくなる事

結局どれを使うか

先に書いた通り、今から新規に開発するのであればやはり「NEW戦法」でしょう。

仮にNEWを使い忘れてnewにしてしまった場所でメモリリークが発生したとしても、それ自体は検出されます(行番号は出ませんが

その後でNEWを忘れてnewにしている場所を探せば良いでしょう。

既に開発が進んでしまったとか、「NEW」と言うオレオレ演算子を使いたくないとかなら「#undef戦法」

基本的にこの2つの中のどちらかになるでしょう。

何か良い解決方法はない物か……

 - プログラミング