none
#pragma init_seg(compiler) に初期化の早さで勝ちうるのは#pragma init_seg(compiler) だけでしょうか? RRS feed

  • 質問

  • 先日不定期的にちょくちょく行っているメモリリークのチェックを再度行ったのですが、前回までには出ていなかったメモリリークが4か所検出されました。(16bytesが2つと56bytesが2つ) ただ

    Dumping objects ->

    {何番目の確保か} normal block at ~

    の{}内の数が異様に少なく(現状何をやっても確実に145~148番目の4つがリークと判定)エントリーポイント(WinMain関数)の開幕と同時に

    new int[0];

    と書いても

    {151} normal block at アドレス, 0 bytes long. で、及びません。また、145~148番目はソースや行数が書かれていなくて謎です。(実際には自分で確保関数を書いた部分はnewなどの直書きではなくほぼすべて置き換えマクロになっていて、プリプロセッサに独自メモリリーク検出指定をすることでファイルや行数を表示できるようにしてあります。)

    従って、少なくとも静的変数に原因があると思いましたが、状況整理のために空の別プロジェクトを新規作成し、x64用にexeファイルとしてデバッグビルドする設定で、main.cppのみ作り

    #include <stdio.h>
    #include <crtdbg.h>
    
    struct LeakTest {
    	void* p;
    	LeakTest(){
    		p = _malloc_dbg( 0, _NORMAL_BLOCK, __FILE__, __LINE__ );		
    		//_CrtSetBreakAlloc(目的の番号); //①
    	}
    	//~LeakTest(){ _free_dbg(p,_NORMAL_BLOCK); } //②
    }
    #pragma init_seg(compiler) 
    abc;
    
    int main( void ){	
    	_CrtSetDbgFlag(_CrtSetDbgFlag(0) | _CRTDBG_LEAK_CHECK_DF);
    	return 0;
    }


    こういう感じのコードを書いてみました。(通常はinit_set(compiler)はuserが使うべきではないはずですが、調査の意味合いです。)結果は

    Dumping objects ->

    main.cpp(7) : {68} normal block at アドレス, 0 bytes long.

    となり、またコードを多少いじりながら別の静的変数をいくつかスコープを変えつつ作成しても、abcの初期化がそれより前に実行されることを確認しました。

    また、この非常にコンパクトなプロジェクトにおいては、②のデストラクタをアンコメントすると、リークが検出されるという判定ではなくなりました。(つまり#pragma init_seg(compiler)のオブジェクトでもプロセス終了時に解放されるという判定になっているということのはず)

    さらに、②を再度コメントアウトしLeakTestオブジェクトを#pragma init_seg(compiler)で、静的領域にもうひとつ作るようにしておいて、①をアンコメントしつつ"目的の番号"に69を指定すると、ブレークポイントが発生しました。発生後も継続とすると{69}{68}でメモリリーク、となったので、この状況ではLeakTestコンストラクタ内の_malloc_dbgで発生させられたことは間違いないはずです。


    というわけで再度元のプロジェクトに戻って、①も再度コメントアウトした上記LeakTestをコピペして試してみたのですが
    LeakTestでのリークの番号が{151} になって、145~148がそのままの番号で出続けていることには依然影響がなく、これでも及びませんでした。

    この145以前の段階に_CrtSetBreakAllocを仕込めれば特定できるはず!と思ったのですが、#pragma init_seg(compiler)より前でないといけない、ということは、そのリークする処理自体がinit_seg(compiler)の影響を受けている以外に考えられないのでは?と思いました。

    まずこの認識は間違っているでしょうか?

    今まで自分で#pragma init_seg(compiler)などを書いたことは一度もなく(今回調べていて知った)、もしかしたら何かのライブラリでの、何かの条件で発動し得るリークなのではないか…?という気もしなくはなくなってきています。(ただ最近移植の際に色々いじったり追加したりしたので、何が原因か現状絞れていません。)

    (16+56)*2 bytes だけではあるので、まず問題とはならないとは思いますが、リーク調査の時に毎回表示されるのもモヤモヤ感がありますし、解消できるならしておきたいのですが、この状況で調べるにあたって何か効率の良さそうな方法はあるでしょうか?

    -------------

    尚、以下のことはわかりました。

    上記新プロジェクトのコードに

    #include <iostream>

    および、main関数のreturn 0;の直前に

    std::cout << &abc;

    を書くことで、LeakTestオブジェクトabcによる確保番号が{68}→{145}になりました。(145番は元のプロジェクトのリーク開始位置と全く同じ)

    元のプロジェクトの方で、LeakTestのコードを消して元に戻した後

    #include <vector>

    および、エントリポイント(WinMain)の開始直後に

    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, int showCmd){ static std::vector<int> v; v.push_back(1); v.clear();

    //以下_CrtSetDbgFlag(_CrtSetDbgFlag(0) | _CRTDBG_LEAK_CHECK_DF);などを含むコード

    のように、簡単なstd::vectorのコードを3行追加すると、{152}  4 bytes および {151} 16 bytes のリークが発生しました。( 145~148はあくまでそのまま ) これは

    v.~vector();

    のように、明示的にデストラクタを読んでやることで、リークがなくなりました。

    ただし、上記新規作成したプロジェクトでこのコードを追加しても、明示的なデストラクタの呼び出しなしでもリークは検出されませんでした。(プロセス終了の一瞬前、リークはどちらもしていないが、潜在的に「検出され得る」ということなのか、それとも何かの条件で本当にリークするということなのか…は現状わかっていません。)

    • 編集済み mr.setup 2013年1月23日 10:37
    2013年1月23日 10:32

回答

  • 失礼ながら長いので読んでませんが、手っ取り早く、user mode stack trace database をオンにしてその block を確保した人を見ればすむことでは。
    • 回答としてマーク mr.setup 2013年1月23日 13:44
    2013年1月23日 11:58
  • リークではなく初期化では?

    VC\crt\src\crt0.c に初期化処理は全て書かれています。ここで定義されるwmainCRTStartupやmainCRTStartupはリンカーオプション/ENTRYのデフォルト値にほかならず、これより前には処理はありません。またこのファイルではWinMain()を呼び出していますので間で暗黙の処理が行われることもありません。

    あとは追いかけてみてください。

    • 回答としてマーク mr.setup 2013年1月23日 13:45
    2013年1月23日 13:07
  • 145~148番目に確保したメモリが解放されないのがどういう手順でチェックしても確実なら、malloc 内部の実際のアロケータ部分に、ブレークポイントを張って、該当アロケート番号のオブジェクトの管理をソースレベルで追いなおしてみればいいのではないでしょうか?

    dbgheap.c の _heap_alloc_dbg_impl 関数(VS2010/2012とも同じ名前)が、CRTのアロケータ本体です。

    ここの中を一度ステップ実行してみればわかりますが、 _lRequestCurr に、通しのアロケート番号出ています。145~148番目に確保したオブジェクトが解放されないのなら、参照箇所があるので、その部分で _lRequestCurr >= 145 となるようにブレークポイントを仕掛けてデバッグ実行すれば、一発で呼び出し元が分かります。

    あてずっぽうに変なコードを書くよりも簡単確実に呼び出し箇所を突き止めることができるので、この方法はかなりおすすめです。

    特に早いタイミングであればあるほど、アロケート番号は安定しますので。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    • 回答としてマーク mr.setup 2013年1月23日 13:45
    2013年1月23日 13:32
  • 一般的な確率から言って、別に ntdll が主体的にメモリ確保してるわけではなく、ntdll は誰かに頼まれているだけ(誰かがメモリ確保したくて ntdll!RtlAllocateHeap 等を呼んでいるだけ)なのではないのでしょうか。

    確認された call stack をここに書いたほうがよいと思います。


    • 編集済み HomeCloset 2013年1月23日 14:37 typo
    • 回答としてマーク mr.setup 2013年1月23日 16:23
    2013年1月23日 14:27
  • std::list の中ということは、std::allocator<T> からの呼び出しと思われます。テンプレートクラスなので、どこで使われているかはさらにその上までたどらないと厳しいでしょう。

    145-148の3つが解放されず、145がlistの中ということは、残る二つはおそらくそのlistに渡されたオブジェクトのコピー処理でアロケートされていると思われます。

    デバッグヒープでブレイクできるということは、デバッグ版コードの中での呼び出しということになります。

    ntdll.dll はスタックトレースの最上位ではありませんか?

    スタートアップルーチンの呼び出し元は ntdll だったと思います。そこまでたどっても意味はありませんので、その手前(メッセージハンドラではないので、OSにまで戻る必要はない)のアロケートルーチンを探す必要があります。

    まぁ止めたらそのままステップアウトして、アロケータを呼び出しているところを探してみるのがよいと思いますよ。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    • 回答としてマーク mr.setup 2013年1月23日 16:22
    2013年1月23日 15:24
  • メモリーリークを起こしているのは

    Concurrency::details::_D3D_temp_staging_buffer_cache

    ですね。

    このオブジェクトは、vcamp110d.dll でアロケートされています。おそらく間接的に。

    このDLLは少しわかりやすく区切ると VC AMP 110 D です。

    C++AMPのDLLなのですがアプリで利用していますか?

    利用している場合は、初期化処理とか終了処理とかその辺に抜けがないかを確認してください。

    また、外部変数か何かにAMP関係のオブジェクトを持たせているとかはありませんか?C++AMPのバグの可能性もありますが(Connectとかもチェックして見るのをお勧めします)、いずれにしても詳細は調査してみないと。。。ですね。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    • 回答としてマーク mr.setup 2013年1月23日 17:57
    2013年1月23日 16:45
  • あーーー

    ふと見てみたら、とんでもなく初歩的な「判断ミス」であったことがわかりましたw

    追加報告です。実際にはライブラリの不具合でも何でもありませんでした。結論から言ってしまうと

    #include <amp_math.h>

    を追加したところで、アプリケーション終了する瞬間には「メモリリーク」などしていなかった!ということです。大変お騒がせしました。これは普通に自由に使うことが可能です。

    なぜ表示されていたかというと,WinMain関数の最後のへん(アプリケーション終了直前)に

    #ifdef MY_CHECK_MEMLEAK
    	Debug::f( _CrtDumpMemoryLeaks() );
    #endif
    
    	return 0;
    
    }

    こんなコードを書いていたことを忘れていました。Debug::fはデバッグの時だけ使える独自関数で、OutputDebugStringを簡単に呼び出せるように複数のオーバーロードを用意してあるものです。この場合は、この呼び出し時点で未解放ものがある場合は列挙して1を表示、ない場合は0だけが表示されます。

    これは

    #ifdef MY_CHECK_MEMLEAK
        _CrtSetDbgFlag(_CrtSetDbgFlag(0) | _CRTDBG_LEAK_CHECK_DF);
    #endif

    だけだと、ちゃんとリーク検出が機能してるか分かりづらかったので追加したもので、MY_CHECK_MEMLEAKというのは、単にメモリリークを検出したくなった時だけ定義するマクロです。


    実は、今まででは、対象のソリューションは、この時点での呼び出しで0が表示されるという風になっていました。その1個前の行が

    	InitClean::CleanupClasses();
    	
    #ifdef MY_CHECK_MEMLEAK
    	Debug::f( _CrtDumpMemoryLeaks() );
    #endif
    
    	return 0;
    
    }
    
    

    こうなっていて、このInitClean::CleanupClasses()で、静的データ(ポインタやWin32APIで使うハンドル)に対して解放を働きかけ、そのメンバとして次々と動的なメンバが解放され、すべて解放済みとなる、という風だったので問題はなかったのですが、このDebug::f呼び出しをなくすことで、C++AMPのライブラリを使用しても対象ソリューションでリークなしと判定されました。これは

    #include <amp_math.h>

    の結果リンクされるvcamp110d.dllでは、静的データとしておそらくstd::listがあるような形で、「この時点では」それが解放されていない、ということを示していたにすぎない、と確信しました。

    また

    	static std::vector<int> v;
    	v.push_back(1);
    	v.clear();

    という実験コード後に

    Debug::f( _CrtDumpMemoryLeaks() );

    が実行された場合でも同様に表示されたのは、clear()呼び出し時点では、内部的にバッファか何らかのオブジェクトはまだ確保済みなのである、と考えれば納得がいきます。

    • 回答としてマーク mr.setup 2013年1月30日 20:53
    • 編集済み mr.setup 2013年1月30日 20:57
    2013年1月30日 20:53

すべての返信

  • 失礼ながら長いので読んでませんが、手っ取り早く、user mode stack trace database をオンにしてその block を確保した人を見ればすむことでは。
    • 回答としてマーク mr.setup 2013年1月23日 13:44
    2013年1月23日 11:58
  • リークではなく初期化では?

    VC\crt\src\crt0.c に初期化処理は全て書かれています。ここで定義されるwmainCRTStartupやmainCRTStartupはリンカーオプション/ENTRYのデフォルト値にほかならず、これより前には処理はありません。またこのファイルではWinMain()を呼び出していますので間で暗黙の処理が行われることもありません。

    あとは追いかけてみてください。

    • 回答としてマーク mr.setup 2013年1月23日 13:45
    2013年1月23日 13:07
  • 145~148番目に確保したメモリが解放されないのがどういう手順でチェックしても確実なら、malloc 内部の実際のアロケータ部分に、ブレークポイントを張って、該当アロケート番号のオブジェクトの管理をソースレベルで追いなおしてみればいいのではないでしょうか?

    dbgheap.c の _heap_alloc_dbg_impl 関数(VS2010/2012とも同じ名前)が、CRTのアロケータ本体です。

    ここの中を一度ステップ実行してみればわかりますが、 _lRequestCurr に、通しのアロケート番号出ています。145~148番目に確保したオブジェクトが解放されないのなら、参照箇所があるので、その部分で _lRequestCurr >= 145 となるようにブレークポイントを仕掛けてデバッグ実行すれば、一発で呼び出し元が分かります。

    あてずっぽうに変なコードを書くよりも簡単確実に呼び出し箇所を突き止めることができるので、この方法はかなりおすすめです。

    特に早いタイミングであればあるほど、アロケート番号は安定しますので。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    • 回答としてマーク mr.setup 2013年1月23日 13:45
    2013年1月23日 13:32
  • どうも、ご回答ありがとうございます。

    user mode stack trace database というのはDebugging Tools for Windows絡みのやつでしょうか?Visual Studio 2012からWinDbg が統合される(Visual StudioでWinDbgのエンジンを用いたデバッグができる)ようになったらしいですね。WDK 8は拡張機能なので、Express以外で使うことができる・・・ただ、最近プリインストール版Windows 8 Proを買ったばかりでそんなにバンバン買える余裕が現状ないため、Expressを使わせていただいています。(8については数点を除けば申し分ないが、ダウングレード可能性ありと別スレで書きましたが、VC++2012の順当進化っぷりは現状相当高く評価しています。まだKinnect for Windowsも買わないといけない予定なのですが、余裕ができたら購入すると思います。)

    そこで、先ほどデバッグツールを別途スタンドアロン用にダウンロードしていたのですが、その最中に、VS2012Express上で簡単にできそうな手順を思いついたので、試してみました。(以下の「具体的」な部分は環境依存だと思います。)


    1.まずは元のプロジェクトに_CrtSetBreakAlloc(151以降);を書き足して、いい感じのところでブレークしてもらいます。

    2.dbgheap.c上の393行を指しました。(やったね)

    3.このとき、lRequest変数が151となっていたので、ここに確保番号が入るはずと予想。

    4.そこで、ブレークポイントを392行の比較行に貼り、ブレークポイントマークの赤丸を右クリック→条件に「145 <= lRequest && lRequest <= 148」を書きました。

    5.これで、一旦アプリを終了し、再度デバッグ開始。lRequest==145で最初にとまるので

    6.デバッグ→ウインドウ→呼び出し履歴 の、呼び出し履歴ウインドウをさかのぼりました。


    これで、ある程度まで原因がわかりました。

    ・std::listの中
    ・D3Dという文字がちらついていた
    ・呼び出し元のdllはC:\Windows\System32\ntdll.dllっぽい。

    ntdll.dllのロード以前はそこからは追えませんでした。(いや、追え「なさそう」でした)

    どっちにしても、現状これらからみるとuserサイドで対処しなきゃいけない類のことではなさそう(というか無理?)という気がしなくもないです。
    デバッグツールを使えばどうにかなりそうでしょうか?

    2013年1月23日 13:44
  • 佐祐理さん、とっちゃんさんもご回答ありがとうございます。

    上のレスは1時間弱ほど時間をかけて色々確認しながら書いていたので、入れ違いましたが、偶然か必然か、とっちゃんさんのご回答の方法と完全に被ったので、これはなかなか有力な方法であったのかなと、確認できました。

    ただ、ここからの判断はちょっとわからないのですが、これはntdll.dllの修正を待つ、ぐらいでしょうか?(これはさすがにdllを読み込んでいる以上スタティックリンクライブラリじゃないはずなので、この100バイト強のリークはユーザーの環境によっては目を瞑るべき・・・ということ・・・?)

    ※_CrtSetDbgFlag(_CrtSetDbgFlag(0) | _CRTDBG_LEAK_CHECK_DF);で分かったことなので…リーク以外の可能性があるのでしょうか?

    • 編集済み mr.setup 2013年1月23日 14:06
    2013年1月23日 13:50
  • 一般的な確率から言って、別に ntdll が主体的にメモリ確保してるわけではなく、ntdll は誰かに頼まれているだけ(誰かがメモリ確保したくて ntdll!RtlAllocateHeap 等を呼んでいるだけ)なのではないのでしょうか。

    確認された call stack をここに書いたほうがよいと思います。


    • 編集済み HomeCloset 2013年1月23日 14:37 typo
    • 回答としてマーク mr.setup 2013年1月23日 16:23
    2013年1月23日 14:27
  • std::list の中ということは、std::allocator<T> からの呼び出しと思われます。テンプレートクラスなので、どこで使われているかはさらにその上までたどらないと厳しいでしょう。

    145-148の3つが解放されず、145がlistの中ということは、残る二つはおそらくそのlistに渡されたオブジェクトのコピー処理でアロケートされていると思われます。

    デバッグヒープでブレイクできるということは、デバッグ版コードの中での呼び出しということになります。

    ntdll.dll はスタックトレースの最上位ではありませんか?

    スタートアップルーチンの呼び出し元は ntdll だったと思います。そこまでたどっても意味はありませんので、その手前(メッセージハンドラではないので、OSにまで戻る必要はない)のアロケートルーチンを探す必要があります。

    まぁ止めたらそのままステップアウトして、アロケータを呼び出しているところを探してみるのがよいと思いますよ。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    • 回答としてマーク mr.setup 2013年1月23日 16:22
    2013年1月23日 15:24
  • 再度ご回答ありがとうございます。Dependency walkerで調べたところ、ntdll.dllはいろんなシステム関連のライブラリから引っ張りだこだとわかったので、そういう観点では難しいと考えています。

    コールスタックについて、ブレーク時点で呼び出し履歴ウインドウに表示されたのはこれだけです。


    ------

    msvcr110d.dll!_heap_alloc_dbg_impl(unsigned __int64 nSize, int nBlockUse, const char * szFileName, int nLine, int * errno_tmp) 行 392    C++
         msvcr110d.dll!_nh_malloc_dbg_impl(unsigned __int64 nSize, int nhFlag, int nBlockUse, const char * szFileName, int nLine, int * errno_tmp) 行 239    C++
         msvcr110d.dll!_nh_malloc_dbg(unsigned __int64 nSize, int nhFlag, int nBlockUse, const char * szFileName, int nLine) 行 302    C++
         msvcr110d.dll!malloc(unsigned __int64 nSize) 行 56    C++
         msvcr110d.dll!operator new(unsigned __int64 size) 行 59    C++
         vcamp110d.dll!std::_Allocate<std::_List_node<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,void * __ptr64> >(unsigned __int64 _Count, std::_List_node<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,void *> * __formal) 行 28    C++
         vcamp110d.dll!std::allocator<std::_List_node<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,void * __ptr64> >::allocate(unsigned __int64 _Count) 行 592    C++
         vcamp110d.dll!std::_Wrap_alloc<std::allocator<std::_List_node<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,void * __ptr64> > >::allocate(unsigned __int64 _Count) 行 877    C++
         vcamp110d.dll!std::_List_alloc<0,std::_List_base_types<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,std::allocator<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry> > >::_Buynode0(std::_List_node<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,void *> * _Next, std::_List_node<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,void *> * _Prev) 行 788    C++
         vcamp110d.dll!std::_List_alloc<0,std::_List_base_types<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,std::allocator<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry> > >::_Buyheadnode() 行 774    C++
         vcamp110d.dll!std::_List_alloc<0,std::_List_base_types<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,std::allocator<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry> > >::_List_alloc<0,std::_List_base_types<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,std::allocator<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry> > >(const std::allocator<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry> & __formal) 行 727    C++
         vcamp110d.dll!std::_List_buy<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,std::allocator<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry> >::_List_buy<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,std::allocator<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry> >(const std::allocator<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry> & _Al) 行 829    C++
         vcamp110d.dll!std::list<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,std::allocator<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry> >::list<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,std::allocator<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry> >() 行 899    C++
         vcamp110d.dll!Concurrency::details::_D3D_temp_staging_buffer_cache::_D3D_temp_staging_buffer_cache(unsigned __int64 _Cached_buffer_size, unsigned __int64 _Max_cached_buffers_total_size) 行 364    C++
         vcamp110d.dll!`dynamic initializer for 'Concurrency::details::_D3D_accelerator_view_impl::_S_large_temp_staging_buffer_cache''() 行 187    C++
         msvcr110d.dll!_initterm(void (void) * * pfbegin, void (void) * * pfend) 行 894    C
    >    vcamp110d.dll!_CRT_INIT(void * hDllHandle, unsigned long dwReason, void * lpreserved) 行 300    C
         vcamp110d.dll!__DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) 行 502    C
         vcamp110d.dll!_DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) 行 473    C
         ntdll.dll!000007fdb253ba0e()    不明
         ntdll.dll!000007fdb25582dc()    不明
         ntdll.dll!000007fdb255787f()    不明
         ntdll.dll!000007fdb255b64e()    不明
         ntdll.dll!000007fdb256319c()    不明
         ntdll.dll!000007fdb256216a()    不明
         ntdll.dll!000007fdb25532ae()    不明


    ------

    何かやったら最後のntdll.dllの不明なやつがこういう風に変わりました

         ntdll.dll!LdrpCallInitRoutine()    不明
         ntdll.dll!LdrpInitializeNode()    不明
         ntdll.dll!LdrpInitializeGraph()    不明
         ntdll.dll!LdrpInitializeGraph()    不明
         ntdll.dll!LdrpInitializeProcess()    不明
         ntdll.dll!string "(ROUND_UP_TO_POWER2(Size, PAGE_S"...    不明
         ntdll.dll!LdrInitializeThunk()    不明

    が、これに関してはそう有用な情報でもないように思います。また、ここに列挙されているDLL

    msvcr110d.dll, vcamp110d.dll,ntdll.dll

    はユーザーサイドのそれではないので、ここに表示されている範囲内でユーザーに改善の余地があるとは思われません。そして

    std::_Allocate<std::_List_node<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,void * __ptr64> >(unsigned __int64 _Count, std::_List_node<Concurrency::details::_D3D_temp_staging_buffer_cache::_Cache_entry,void *> * __formal)

    このように、std::list関連のアロケーター付近のテンプレート指定に”D3D”という文字がちらついているのですが、該当プロジェクトにおいてはDirect3Dは直接は使用しておらず、またDirect2Dも使ってない状況でもこうなるので、勘としては、Vista以降の、GDI内部でDirect3Dに変換する処理・・・というのにかかわってるのではないかと思います。

    ※該当プロジェクトにてgdi32.libは静的リンクしています。
    ※もともと#pragma init_seg( compiler )で勝てなかったというのもありますし、これはユーザーサイドではやはりできない問題・・・じゃないのかなぁ。(開発環境とかをどこかで予期しないインストール手順を私がしたために、システム関連のdllが古いやつとかに一部上書きされて…とかいう可能性がもしあるなら、ある意味納得といえるかもしれませんが)

    • 編集済み mr.setup 2013年1月23日 16:24
    2013年1月23日 16:22
  • メモリーリークを起こしているのは

    Concurrency::details::_D3D_temp_staging_buffer_cache

    ですね。

    このオブジェクトは、vcamp110d.dll でアロケートされています。おそらく間接的に。

    このDLLは少しわかりやすく区切ると VC AMP 110 D です。

    C++AMPのDLLなのですがアプリで利用していますか?

    利用している場合は、初期化処理とか終了処理とかその辺に抜けがないかを確認してください。

    また、外部変数か何かにAMP関係のオブジェクトを持たせているとかはありませんか?C++AMPのバグの可能性もありますが(Connectとかもチェックして見るのをお勧めします)、いずれにしても詳細は調査してみないと。。。ですね。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    • 回答としてマーク mr.setup 2013年1月23日 17:57
    2013年1月23日 16:45
  • なんと!確かに VC AMP 110 Dですねw 見えてたはずだったのに見えてませんでしたw

    ありがとうございます♪

    確か1週間ほど前にsincos関数を試してみようとしてたのですが、結局restrict(amp)がどういう状況で指定できるのか、状況別には完全にわかりきらず、結果その時実験できず、他にも調べるべきことが山のようにあったので、とりあえず主なコードはすべて消しておいたのです。

    しかし、たった一行だけ消していなかった行がありました。

    #include <amp_math.h>

    これだけで・・・?そんな馬鹿なとも思うのですが、消したら見事該当プロジェクトでのリークがなくなり、完全に健全な状態に戻りました。それ以外は一文字たりともいじっていません。(ええっ)

    ただ、新規プロジェクトの方をもっとかるーくして、main.cppの中身を

    #include <crtdbg.h>
    #include <amp_math.h>
    
    int main(void){
        _CrtSetDbgFlag(_CrtSetDbgFlag(0) | _CRTDBG_LEAK_CHECK_DF);
        return 0;
    }

    にしても、それだけでは発動しなかったので、何か発動するギミックがあるのだと思うのですが(コンパイルオプションかリンクのオプションとかの可能性・・・?)なにもC++ AMP関連のヘッダのインクルードなくして、dllも読み込まれない状態で、他の部分修正せずに元のプロジェクトのビルドが通ってしまう以上、変数・関数など、何もそれ関連のものは使ってないことは確か・・・のはずに思います。(プリプロセッサでたまたま予期しないことになってない限りは)

    思ったよりも時間を食ってしまったので、すぐにさらに調べまくるのは難しい状況に個人的になっているため、今回は一旦使わない方向様子見としておき、使いたい状況になったらもうちょい調べてみようと思います。(詳細が分かった方がいらっしゃいましたら教えていただけるとありがたいです。)

    • 編集済み mr.setup 2013年1月23日 19:07
    • 回答としてマーク mr.setup 2013年1月23日 19:07
    • 回答としてマークされていない mr.setup 2013年1月23日 19:20
    2013年1月23日 17:57
  • あーーー

    ふと見てみたら、とんでもなく初歩的な「判断ミス」であったことがわかりましたw

    追加報告です。実際にはライブラリの不具合でも何でもありませんでした。結論から言ってしまうと

    #include <amp_math.h>

    を追加したところで、アプリケーション終了する瞬間には「メモリリーク」などしていなかった!ということです。大変お騒がせしました。これは普通に自由に使うことが可能です。

    なぜ表示されていたかというと,WinMain関数の最後のへん(アプリケーション終了直前)に

    #ifdef MY_CHECK_MEMLEAK
    	Debug::f( _CrtDumpMemoryLeaks() );
    #endif
    
    	return 0;
    
    }

    こんなコードを書いていたことを忘れていました。Debug::fはデバッグの時だけ使える独自関数で、OutputDebugStringを簡単に呼び出せるように複数のオーバーロードを用意してあるものです。この場合は、この呼び出し時点で未解放ものがある場合は列挙して1を表示、ない場合は0だけが表示されます。

    これは

    #ifdef MY_CHECK_MEMLEAK
        _CrtSetDbgFlag(_CrtSetDbgFlag(0) | _CRTDBG_LEAK_CHECK_DF);
    #endif

    だけだと、ちゃんとリーク検出が機能してるか分かりづらかったので追加したもので、MY_CHECK_MEMLEAKというのは、単にメモリリークを検出したくなった時だけ定義するマクロです。


    実は、今まででは、対象のソリューションは、この時点での呼び出しで0が表示されるという風になっていました。その1個前の行が

    	InitClean::CleanupClasses();
    	
    #ifdef MY_CHECK_MEMLEAK
    	Debug::f( _CrtDumpMemoryLeaks() );
    #endif
    
    	return 0;
    
    }
    
    

    こうなっていて、このInitClean::CleanupClasses()で、静的データ(ポインタやWin32APIで使うハンドル)に対して解放を働きかけ、そのメンバとして次々と動的なメンバが解放され、すべて解放済みとなる、という風だったので問題はなかったのですが、このDebug::f呼び出しをなくすことで、C++AMPのライブラリを使用しても対象ソリューションでリークなしと判定されました。これは

    #include <amp_math.h>

    の結果リンクされるvcamp110d.dllでは、静的データとしておそらくstd::listがあるような形で、「この時点では」それが解放されていない、ということを示していたにすぎない、と確信しました。

    また

    	static std::vector<int> v;
    	v.push_back(1);
    	v.clear();

    という実験コード後に

    Debug::f( _CrtDumpMemoryLeaks() );

    が実行された場合でも同様に表示されたのは、clear()呼び出し時点では、内部的にバッファか何らかのオブジェクトはまだ確保済みなのである、と考えれば納得がいきます。

    • 回答としてマーク mr.setup 2013年1月30日 20:53
    • 編集済み mr.setup 2013年1月30日 20:57
    2013年1月30日 20:53