C ++ / CLIでのnew / deleteのグローバルな再定義の問題

ご存じのとおり、C ++を使用すると、new演算子とdelete演算子をグローバルに再定義できます。 通常、このオーバーライドは、メモリリークの診断、検索、およびより効率的なメモリの割り当てに使用されます。

このすべてを大規模プロジェクトで使用します。 ただし、C ++ / CLIを使用して、C ++の主要部分とやり取りするC#で記述された部分があります。 そして、ここに問題がありました。 できなかったメモリリークが発生しました。

それはすべて、新しいものが呼び出されたという事実に帰着しましたが、削除はcrt'shnyでした。
問題に対処するために、状況をシミュレートするテストソリューションを作成しました。 簡単にするために、オーバーライドされた関数は次のようになります。
void * __cdecl operator new ( size_t size )
{
printf( "New\n" );
return malloc(size);
}

void __cdecl operator delete( void *p )
{
printf( "Delete\n" );
free(p);
}


そのため、出力には、新規/削除ペアの数と同じ量のNewとDeleteを取得する必要があります。

new / deleteを呼び出すコードは次のとおりです。
//managed.h
namespace Managed
{
public ref class ManagedClass
{
public :
ManagedClass()
{
NativeClass* cl = new NativeClass();
delete cl;
int * a = new int ();
delete a;
}
};
}

//native.cpp
NativeClass::NativeClass()
{
int * a = new int ();
delete a;
}


C#から、次のようなManagedClassを作成します。
static void Main( string [] args )
{
ManagedClass cl = new ManagedClass();
}


別のbasic.hファイルにあるクラスFooがまだありますが、これらのクラスでは使用されません。 その定義だけがプリコンパイル済みヘッダーに配置されます。
この「悪い」クラスには機能はありませんが、その存在だけで非常に奇妙な結果になります。 クラスコピーコンストラクターにコメントすると、すべて正常に機能します。
#pragma once

class Foo
{
public :
Foo() {}
//Foo( const Foo& ) {}
Foo& operator = ( const Foo& ) { return * this ; }

virtual ~Foo() {}
};


結論:
新しい
新しい
削除する
削除する
新しい
削除する

新規3-削除3。 すべてが順調です。

コピーコンストラクターがコメントアウトされていない場合、新しいすべてが削除に対応するわけではありません。
#pragma once

class Foo
{
public :
Foo() {}
Foo( const Foo& ) {}
Foo& operator = ( const Foo& ) { return * this ; }

virtual ~Foo() {}
};


結論:
新しい
新しい
削除する
新しい

新規3-削除1。 すべてが悪いです。

結論:削除機能の選択にエラーがあります。これは「月の位置」に依存します。 グーグルを開始する時間です。

検索中に、私たちの問題を記述したとされる文書が見つかりました。
http://support.microsoft.com/kb/122675。

残念ながら、ドキュメントを詳細に読んだ後、私たちのケースは検討したケースとは異なることがわかりました。 さらに、pure-C ++プロジェクトでは、この動作は観察されません。 問題はC ++ / CLIにあります。

私たちは研究を続けています。 デストラクタが非仮想化されている場合、再びすべてが正常に機能します。
#pragma once

class Foo
{
public :
Foo() {}
Foo( const Foo& ) {}
Foo& operator = ( const Foo& ) { return * this ; }

~Foo() {}
};


結論:
新しい
新しい
削除する
削除する
新しい
削除する

このクラスがプログラムで使用されていないことは注目に値します。 まさにその存在がバグにつながります。
なぜなら バグはC ++ / CLIでのみ表示され、このクラスを管理対象外としてマークしようとします。
#pragma managed(push,off)
#include "../System/basic.h" // h “” Foo
#pragma managed(pop)


結論:
新しい
新しい
削除する
削除する
新しい
削除する

わかった! ただし、これは問題の解決策ではありません。 さらに掘り下げます。

プリコンパイル済みヘッダーの問題ですか? ただし、basic.hがStdafx.hからのもので、プロジェクトに入る他の* .hファイルに貼り付けた場合、問題は再発します。

リンカの機能を詳しく見てみましょう。 これを行うには、リンカーから追加情報を出力するモードを有効にします。
画像

わあ! 彼はMSVCRTD.libで削除を見つけました。そのため、削除は削除されません。
画像

デストラクタを非仮想にするか、コピーコンストラクタにコメントする場合、リンカの出力を取得します。
画像

さらに、コピーコンストラクターを終了し、デストラクターを非仮想化するが、仮想関数を追加すると、削除が再び失敗し始めます。

調査はデバッグアセンブリで行われましたが、リリースでも同じ動作が観察されました。

残念ながら、この問題はまだ解決されていないため、プロジェクトのメモリリークを簡単に検索することはできません。

その結果、仮想テーブルとコピーコンストラクター(およびこのクラスがマネージにコンパイルされる)を持つクラスがある場合、dllで上書きされた削除がありますが、リンカーは標準の削除をリンクします。

PS。 問題に対する最も簡単なテストソリューションを独自に使用することに興味があり、おそらくアドバイスがあれば、こちらからダウンロードできます: https : //mysvn.ru/Lof/NewDeleteProblem

Source: https://habr.com/ru/post/J99142/


All Articles