10

私は可変長構造 (TAPI) を使用するいくつかのレガシー C++ コードに取り組んできました。構造のサイズは可変長文字列に依存します。構造体は、次のように配列をキャストすることによって割り当てられnewます。

STRUCT* pStruct = (STRUCT*)new BYTE[sizeof(STRUCT) + nPaddingSize];

ただし、後でdelete呼び出しを使用してメモリを解放します。

delete pStruct;

new[]この配列と非配列の混合はdeleteメモリリークを引き起こしますか、それともコンパイラに依存しますか? 代わりにmallocこのコードを使用するように変更した方がよいでしょうか?free

4

24 に答える 24

12

技術的には、アロケーターが一致しないと問題が発生する可能性があると思いますが、実際には、この例で正しいことを行わないコンパイラーを知りません。

さらに重要なことに、STRUCTどこにデストラクタがある (または与えられる) 場合、対応するコンストラクタを呼び出さずにデストラクタを呼び出すことになります。

もちろん、pStruct がどこから来たのかわかっている場合は、delete でキャストして割り当てに一致させない理由は次のとおりです。

delete [] (BYTE*) pStruct;
于 2008-09-16T14:50:18.320 に答える
7

個人的には、メモリの管理には を使用したほうがよいと思うstd::vectorので、delete.

std::vector<BYTE> backing(sizeof(STRUCT) + nPaddingSize);
STRUCT* pStruct = (STRUCT*)(&backing[0]);

バッキングがスコープを離れると、あなたpStructはもはや有効ではありません。

または、次を使用できます。

boost::scoped_array<BYTE> backing(new BYTE[sizeof(STRUCT) + nPaddingSize]);
STRUCT* pStruct = (STRUCT*)backing.get();

またはboost::shared_array、所有権を移動する必要がある場合。

于 2008-09-16T15:39:51.323 に答える
6

はい、メモリリークが発生します。

C++ の落とし穴を除いて、これを参照してください。http://www.informit.com/articles/article.aspx?p=30642の理由。

Raymond Chen は、Microsoft コンパイラのカバーの下で、ベクトル バージョンとスカラー バージョンがどのように異なるかについて説明newdeleteています...ここでは: http://blogs.msdn.com/oldnewthing/archive/2004/02/03/66660.aspx

私見では、削除を次のように修正する必要があります。

delete [] pStruct;

malloc/に切り替えるfreeのではなく、間違いを犯さずに簡単に変更できるという理由だけで;)

そしてもちろん、元の割り当てのキャストが原因で、上に示した変更を簡単に行うのは間違っています。

delete [] reinterpret_cast<BYTE *>(pStruct);

だから、結局のところmalloc/に切り替えるのはおそらく同じくらい簡単だと思います ;)free

于 2008-09-16T15:00:47.110 に答える
6

コードの動作は未定義です。運が良ければ (運が悪くても)、コンパイラで動作するかもしれませんが、実際にはそれは正しいコードではありません。これには 2 つの問題があります。

  1. delete配列でなければなりませんdelete []
  2. delete、割り当てられた型と同じ型へのポインターで呼び出す必要があります。

したがって、完全に正しくするために、次のようなことをしたいと考えています。

delete [] (BYTE*)(pStruct);
于 2008-09-16T14:52:55.320 に答える
4

C++ 標準では、次のように明確に述べられています。

delete-expression:
             ::opt delete cast-expression
             ::opt delete [ ] cast-expression

最初の選択肢は非配列オブジェクト用で、2 番目の選択肢は配列用です。オペランドは、ポインター型、またはポインター型への単一の変換関数 (12.3.2) を持つクラス型を持つ必要があります。結果の型は void です。

最初の選択肢 (delete object) では、delete のオペランドの値は非配列オブジェクトへのポインターでなければなりません [...] そうでない場合、動作は未定義です。

in のオペランドの値は、その静的型 ( )delete pStructとは関係なく、 の配列へのポインタです。したがって、コードの形式が正しくないため、メモリ リークに関する議論はまったく無意味であり、この場合、適切な実行可能ファイルを生成するために C++ コンパイラは必要ありません。charSTRUCT*

メモリ リークが発生する可能性もあれば、発生しない可能性もあれば、システムをクラッシュさせる可能性もあります。実際、あなたのコードをテストした C++ 実装では、削除式の時点でプログラムの実行が中止されます。

于 2008-09-16T15:14:07.810 に答える
3

他の投稿で強調されているように:

1) new/delete 割り当てメモリの呼び出しと、コンストラクタ/デストラクタの呼び出し (C++ '03 5.3.4/5.3.5)

2) と の配列/非配列バージョンを混在させるnewと、delete未定義の動作になります。(C++ '03 5.3.5/4)

mallocソースを見ると、誰かがandを検索して置換しfreeたようで、上記が結果です。C++ にはこれらの関数を直接置き換える機能があり、それは and の割り当て関数を直接呼び出すことnewですdelete

STRUCT* pStruct = (STRUCT*)::operator new (sizeof(STRUCT) + nPaddingSize);
// ...
pStruct->~STRUCT ();  // Call STRUCT destructor
::operator delete (pStruct);

STRUCT のコンストラクターを呼び出す必要がある場合は、メモリの割り当てを検討してから、配置を使用できますnew

BYTE * pByteData = new BYTE[sizeof(STRUCT) + nPaddingSize];
STRUCT * pStruct = new (pByteData) STRUCT ();
// ...
pStruct->~STRUCT ();
delete[] pByteData;
于 2008-09-16T16:27:35.903 に答える
2

本当にこの種のことをしなければならない場合は、おそらく演算子newを直接呼び出す必要があります。

STRUCT* pStruct = operator new(sizeof(STRUCT) + nPaddingSize);

このように呼び出すと、コンストラクタ/デストラクタの呼び出しを回避できると思います。

于 2008-09-16T16:20:14.643 に答える
2

キーワード new および delete のさまざまな使用法は、かなりの混乱を引き起こしているようです。C++ で動的オブジェクトを構築するには、常に 2 つの段階があります。生メモリの割り当てと、割り当てられたメモリ領域での新しいオブジェクトの構築です。オブジェクトの有効期間の反対側には、オブジェクトの破棄と、オブジェクトが存在するメモリ位置の割り当て解除があります。

多くの場合、これら 2 つの手順は単一の C++ ステートメントによって実行されます。

MyObject* ObjPtr = new MyObject;

//...

delete MyObject;

上記の代わりに、C++ の raw メモリ割り当て関数operator newoperator delete、(placement を介しnewた) 明示的な構築と破棄を使用して、同等の手順を実行できます。

void* MemoryPtr = ::operator new( sizeof(MyObject) );
MyObject* ObjPtr = new (MemoryPtr) MyObject;

// ...

ObjPtr->~MyObject();
::operator delete( MemoryPtr );

キャストが含まれておらず、割り当てられたメモリ領域に 1 種類のオブジェクトのみが構築されていることに注目してください。生メモリを割り当てる方法としてのようなものを使用することnew char[N]は、論理的にcharは、新しく割り当てられたメモリにオブジェクトが作成されるため、技術的に正しくありません。「うまくいかない」状況については知りませんが、生のメモリ割り当てとオブジェクト作成の区別が曖昧になるため、使用しないことをお勧めします。

この特定のケースでは、 の 2 つのステップを分離してもメリットdeleteはありませんが、初期割り当てを手動で制御する必要があります。MyObject上記のコードは「すべてが機能している」シナリオで機能しますが、のコンストラクターが例外をスローした場合、生のメモリがリークします。これは、割り当ての時点で例外ハンドラーを使用してキャッチおよび解決できますが、配置 new 式によって完全な構築を処理できるように、カスタム演算子 new を提供する方がおそらく適切です。

class MyObject
{
    void* operator new( std::size_t rqsize, std::size_t padding )
    {
        return ::operator new( rqsize + padding );
    }

    // Usual (non-placement) delete
    // We need to define this as our placement operator delete
    // function happens to have one of the allowed signatures for
    // a non-placement operator delete
    void operator delete( void* p )
    {
        ::operator delete( p );
    }

    // Placement operator delete
    void operator delete( void* p, std::size_t )
    {
        ::operator delete( p );
    }
};

ここにはいくつかの微妙な点があります。クラス配置 new を定義して、クラス インスタンスに十分なメモリとユーザー指定可能なパディングを割り当てることができるようにします。これを行うため、一致する配置の削除を提供する必要があります。これにより、メモリの割り当てが成功しても構築が失敗した場合、割り当てられたメモリが自動的に割り当て解除されます。残念ながら、プレースメント削除の署名は、プレースメント以外の削除で許可されている 2 つの署名のいずれかと一致するため、実際のプレースメント削除がプレースメント削除として扱われるように、別の形式の非プレースメント削除を提供する必要があります。(placement new とplacement delete の両方に追加のダミー パラメータを追加することでこれを回避できますが、これにはすべての呼び出しサイトで余分な作業が必要になります。)

// Called in one step like so:
MyObject* ObjectPtr = new (padding) MyObject;

単一の new 式を使用すると、 new 式の一部がスローされてもメモリがリークしないことが保証されます。

オブジェクトの有効期間の反対側では、delete 演算子を定義したため (定義していなかったとしても、オブジェクトのメモリはもともとグローバル演算子 new から取得されたものでした)、動的に作成されたオブジェクトを破棄する正しい方法は次のとおりです。 .

delete ObjectPtr;

概要!

  1. キャストを見てください!operator newraw メモリをoperator delete扱うと、placement new は raw メモリ内にオブジェクトを構築できます。a からオブジェクト ポインターへの明示的なキャストvoid*は、通常、「正常に機能する」場合でも、論理的に何かが間違っていることを示しています。

  2. new[] と delete[] を完全に無視しました。これらの可変サイズのオブジェクトは、どのような場合でも配列では機能しません。

  3. new の配置により、新しい式がリークしないようにすることができます。新しい式は、破棄が必要なオブジェクトと割り当て解除が必要なメモリへのポインターとして評価されます。何らかの種類のスマート ポインターを使用すると、他の種類のリークを防ぐことができる場合があります。deleteプラス面として、これを行う正しい方法としてプレーンを使用できるようにしたため、ほとんどの標準的なスマート ポインターが機能します。

于 2008-09-20T15:19:08.647 に答える
2

@eric - コメントありがとうございます。でも、あなたは何かを言い続けています。

これらのランタイム ライブラリは、OS に依存しない一貫した構文で OS へのメモリ管理呼び出しを処理し、これらのランタイム ライブラリは、Linux、Windows、Solaris、AIX などの OS 間で malloc と新しい作業を一貫して行う役割を果たします。 .

本当じゃない。たとえば、コンパイラ ライターは std ライブラリの実装を提供し、OS に依存する方法でそれらを完全に自由に実装できます。たとえば、malloc への巨大な呼び出しを 1 回行うだけで、ブロック内のメモリを自由に管理できます。

互換性が提供されるのは、std などの API が同じであるためです。ランタイム ライブラリがすべて向きを変えてまったく同じ OS 呼び出しを呼び出すためではありません。

于 2008-09-17T16:27:25.693 に答える
1

私は現在投票できませんが、問題はアロケーターやSTRUCTにデストラクタがあるかどうかとは関係がないため、slicedlimeの回答はRob Walkerの回答よりも望ましいです。

また、サンプル コードが必ずしもメモリ リークを引き起こすとは限らないことにも注意してください。これは未定義の動作です。ほぼすべてのことが起こる可能性があります (何も悪いことから遠く離れた場所でのクラッシュまで)。

サンプル コードでは、未定義の単純な動作が発生します。slicedlime の答えは直接的で的を射ています (ベクトルは STL のものであるため、「ベクトル」という単語を「配列」に変更する必要があることに注意してください)。

この種のものは、C++ FAQ (セクション 16.12、16.13、および 16.14) でかなりよくカバーされています。

http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.12

于 2008-09-16T16:27:07.573 に答える
1

これは、参照している配列の削除 ([]) であり、ベクトルの削除ではありません。ベクターは std::vector であり、その要素の削除を処理します。

于 2008-09-16T16:47:48.647 に答える
0

メモリリークはないと思います。

STRUCT* pStruct = (STRUCT*)new BYTE [sizeof(STRUCT) + nPaddingSize];

これは、オペレーティングシステム内のメモリ割り当て呼び出しに変換され、そのメモリへのポインタが返されます。メモリが割り当てられる時点で、基盤となるオペレーティングシステムに対するメモリ割り当て要求を満たすためにsizeof(STRUCT)、のサイズとのサイズがわかります。nPaddingSize

したがって、割り当てられたメモリは、オペレーティングシステムのグローバルメモリ割り当てテーブルに「記録」されます。メモリテーブルは、ポインタによってインデックスが付けられます。したがって、対応するdeleteの呼び出しでは、最初に割り当てられたすべてのメモリが解放されます。(メモリの断片化は、この分野でも人気のあるテーマです)。

ご覧のとおり、C / C ++コンパイラはメモリを管理しておらず、基盤となるオペレーティングシステムは管理しています。

よりクリーンな方法があることに同意しますが、OPはこれがレガシーコードであると言っていました。

要するに、受け入れられた答えはメモリリークがあると信じているので、メモリリークは見られません。

于 2008-09-16T16:42:08.740 に答える
0

上記の優れた回答に加えて、次のことも追加したいと思います。

コードがLinuxで実行されている場合、またはLinuxでコンパイルできる場合は、Valgrindを使用して実行することをお勧めします。これは優れたツールであり、生成される無数の有用な警告の中で、メモリを配列として割り当ててから非配列として解放するときにも通知します(その逆も同様です)。

于 2008-09-17T15:29:45.393 に答える
0

はい、そうかもしれません。new[] で割り当てた後、delete で割り当てを解除しているので、ここでは malloc/free の方が安全ですが、C++ では (デ) コンストラクターを処理しないため、使用しないでください。

また、コードはデコンストラクターを呼び出しますが、コンストラクターは呼び出しません。一部の構造体では、これによりメモリ リークが発生する可能性があります (コンストラクタが文字列などにさらにメモリを割り当てた場合)。

これにより、コンストラクターとデコンストラクターも正しく呼び出されるため、正しく実行することをお勧めします。

STRUCT* pStruct = new STRUCT;
...
delete pStruct;
于 2008-09-16T14:49:41.537 に答える
0

Len: 問題は、pStruct が STRUCT* であるということですが、割り当てられたメモリは実際には不明なサイズの BYTE[] です。したがって、delete[] pStruct は、割り当てられたすべてのメモリの割り当てを解除しません。

于 2008-09-16T15:04:23.343 に答える
0

@マット・クルックシャンク

「うーん、VS2005 で実験してみると、ベクトル new によって作成されたメモリのスカラー削除から正直なリークを得ることができません。コンパイラの動作はここでは「未定義」であると思います。

それがコンパイラの動作またはコンパイラの問題でさえあることに同意しません。あなたが指摘したように、「new」キーワードはコンパイルされ、ランタイムライブラリにリンクされます。これらのランタイム ライブラリは、OS に依存しない一貫した構文で OS へのメモリ管理呼び出しを処理し、これらのランタイム ライブラリは、Linux、Windows、Solaris、AIX などの OS 間で malloc と新しい作業を一貫して行う役割を果たします。 . これが、移植性の議論に言及した理由です。ランタイムも実際にはメモリを管理していないことを証明しようとしています。

OSはメモリを管理します。

OS へのランタイム ライブラリ インターフェイス。Windows では、これは仮想メモリ マネージャ DLL です。これが、stdlib.h が Linux カーネル ソースではなく GLIB-C ライブラリ内に実装されている理由です。GLIB-C が他の OS で使用されている場合、それは正しい OS 呼び出しを行うための malloc 変更の実装です。VS、Borland などでは、実際にメモリを管理するコンパイラに同梱されているライブラリを見つけることはできません。ただし、malloc の OS 固有の定義が見つかります。

Linux のソースがあるので、そこで malloc がどのように実装されているかを見ることができます。malloc が GCC コンパイラに実際に実装されていることがわかります。GCC コンパイラは、基本的に 2 つの Linux システム コールをカーネルに作成してメモリを割り当てます。決して、実際にメモリを管理する malloc 自体はありません。

そして、私からそれを取らないでください。Linux OS のソース コードを読むか、K&R がそれについて何を言っているかを見ることができます... ここに、C の K&R への PDF リンクがあります。

http://www.oberon2005.ru/paper/kr_c.pdf

ページ 149 の近くを参照してください: 「malloc と free の呼び出しは任意の順序で発生する可能性があります。必要に応じてより多くのメモリを取得するために、malloc はオペレーティング システムを呼び出します。これらのルーチンは、比較的マシンに依存しないコードでマシンに依存するコードを記述する際の考慮事項の一部を示しています。また、構造体、共用体、および typedef の実際のアプリケーションも示します。」

「しかし、元の投稿者が言ったことを実行するのは本当にお粗末な慣行であることを認めなければなりません。」

ああ、私はそこに同意しません。私の要点は、元の投稿者のコードはメモリ リークを助長していないということでした。私が言っていたのはそれだけです。私は物事のベストプラクティスの側面については触れませんでした。コードが delete を呼び出しているため、メモリが解放されています。

あなたの弁護において、元の投稿者のコードが終了しなかった場合、または削除呼び出しに到達しなかった場合、コードにメモリリークが発生する可能性があることに同意しますが、後で削除が呼び出されるのを見ると述べているためです。「ただし、後でメモリは削除呼び出しを使用して解放されます:」

さらに、私が行ったように応答した理由は、OPのコメント「構造サイズが可変長文字列に依存する可変長構造(TAPI)」によるものでした。

そのコメントは、行われているキャストに対する割り当ての動的な性質に疑問を呈しているように聞こえ、その結果、メモリ リークが発生するのではないかと考えていました。もしそうなら、私は行間を読んでいました;)。

于 2008-09-17T15:16:15.600 に答える
0

演算子 new と delete を使用します。

struct STRUCT
{
  void *operator new (size_t)
  {
    return new char [sizeof(STRUCT) + nPaddingSize];
  }

  void operator delete (void *memory)
  {
    delete [] reinterpret_cast <char *> (memory);
  }
};

void main()
{
  STRUCT *s = new STRUCT;
  delete s;
}
于 2008-09-16T15:07:34.690 に答える
0

@ericmayo - クレープ。さて、VS2005 で実験してみると、ベクトル new によって作成されたメモリのスカラー削除から正直なリークを取得できません。ここでは、コンパイラの動作は「未定義」であり、私ができる最善の防御策であると思います。

ただし、元の投稿者が言ったことを実行するのは本当にお粗末な慣行であることを認めなければなりません。

その場合、C++ は現在のように移植性がなくなり、クラッシュしたアプリケーションが OS によってクリーンアップされることはありません。

ただし、この論理は実際には成り立ちません。私の主張は、コンパイラのランタイムは、OS が返すメモリ ブロック内のメモリを管理できるということです。これがほとんどの仮想マシンの仕組みであるため、この場合の移植性に対するあなたの議論はあまり意味がありません。

于 2008-09-17T14:42:15.337 に答える
0

BYTE * と削除にキャストできます。

delete[] (BYTE*)pStruct;
于 2008-09-16T14:52:16.023 に答える
0

リソースの取得と解放をできる限りバランスよく保つことが常に最善です。この場合、漏れているかどうかはわかりませんが。これは、コンパイラのベクトル (デ) 割り当ての実装に依存します。

BYTE * pBytes = new BYTE [sizeof(STRUCT) + nPaddingSize];

STRUCT* pStruct = reinterpret_cast< STRUCT* > ( pBytes ) ;

 // do stuff with pStruct

delete [] pBytes ;
于 2008-09-16T14:54:50.760 に答える
0

@Matt Cruikshank delete[]を呼び出さずにOSをクリーンアップさせることを決して提案しなかったので、注意を払って私が書いたものをもう一度読む必要があります。そして、ヒープを管理する C++ ランタイム ライブラリについて、あなたは間違っています。その場合、C++ は現在のように移植性がなくなり、クラッシュしたアプリケーションが OS によってクリーンアップされることはありません。(C/C++ を非移植性のように見せる OS 固有のランタイムがあることを認めます)。kernel.org の Linux ソースで stdlib.h を見つけてください。C++ の new キーワードは、実際には malloc と同じメモリ管理ルーチンと対話しています。

C++ ランタイム ライブラリは OS システム コールを作成し、ヒープを管理するのは OS です。ランタイム ライブラリがいつメモリを解放するかを示しているという点では部分的に正しいですが、実際にはヒープ テーブルを直接処理するわけではありません。つまり、リンク先のランタイムは、アプリケーションにコードを追加して、ヒープを割り当てまたは割り当て解除するコードを追加しません。これは、Windows、Linux、Solaris、AIX などの場合です。これは、Linux のカーネル ソースで malloc を修正せず、Linux ソースで stdlib.h を見つけられない理由でもあります。これらの最新のオペレーティング システムには仮想メモリ マネージャーがあり、それによって事態がさら​​に複雑になることを理解してください。

1G ボックスで 2G の RAM を求めて malloc を呼び出しても、有効なメモリ ポインターを取得できるのはなぜかと思ったことはありませんか?

x86 プロセッサのメモリ管理は、3 つのテーブルを使用してカーネル空間内で管理されます。PAM (ページ アロケーション テーブル)、PD (ページ ディレクトリ)、および PT (ページ テーブル)。これは、私が話しているハードウェア レベルです。C++ アプリケーションではなく、OS メモリ マネージャーが行うことの 1 つは、BIOS 呼び出しを使用して、起動中にボックスにインストールされている物理メモリの量を調べることです。OS は、アプリケーションにも権限がないメモリにアクセスしようとした場合などの例外も処理します。(GPF 一般保護違反)。

マット、私たちは同じことを言っているのかもしれませんが、ボンネットの下の機能を少し混乱させているのではないかと思います。私は生計を立てるために C/C++ コンパイラを維持していました...

于 2008-09-17T03:23:01.973 に答える
0

あなたは、C と C++ のやり方を混ぜ合わせているようなものです。STRUCT のサイズ以上を割り当てるのはなぜですか? 「新しいSTRUCT」だけではないのはなぜですか?これを行う必要がある場合、この場合は malloc と free を使用する方が明確な場合があります。これは、割り当てられたオブジェクトの型とサイズについて、あなたや他のプログラマーが推測する可能性が少し低くなる可能性があるためです。

于 2008-09-16T14:55:21.570 に答える
-1

ericmayo.myopenid.comは非常に間違っているため、十分な評判のある人は彼に反対票を投じる必要があります。

CまたはC++ランタイムライブラリは、オペレーティングシステムによってブロック単位で与えられたヒープを管理しています。ただし、メモリを解放するためにどのランタイム呼び出しを行う必要があるかをコンパイラに示し、場合によってはそこにあるオブジェクトを破棄するのは開発者の責任です。この場合、C ++ランタイムがヒープを有効な状態のままにするために、ベクトル削除(別名delete [])が必要です。PROCESSが終了したときに、OSが基盤となるメモリブロックの割り当てを解除するのに十分スマートであるという事実は、開発者が信頼すべきものではありません。これは、deleteをまったく呼び出さないようなものです。

于 2008-09-16T19:20:54.550 に答える
-1

ロブ・ウォーカーの返事は良いです。

コンストラクターまたは/およびディストラクターがない場合、基本的に生メモリのチャンクを割り当てて解放する必要がある場合は、free/malloc ペアの使用を検討してください。

于 2008-09-16T14:54:13.757 に答える