29

まず第一に、delete割り当てられたものに対して使用するnew[]と、C++ 標準に従って未定義の動作になります。

Visual C++ 7 では、このようなペアリングは 2 つの結果のいずれかにつながる可能性があります。

new[] された型に自明なコンストラクタとデストラクタがある場合、VC++ はそのブロックnewの代わりに単純にnew[]使用しdelete、そのブロックに対して使用すると正常に動作します- new「メモリの割り当て」をdelete呼び出すだけで、「メモリの解放」を呼び出すだけです。

new[] された型に自明でないコンストラクタまたはデストラクタがある場合、上記のトリックは実行できません。VC++7 は正確に正しい数のデストラクタを呼び出す必要があります。そのため、配列の先頭size_tに要素数を格納します。によって返されるアドレスはnew[]、ブロックの先頭ではなく、最初の要素を指すようになりました。したがって、delete使用されている場合、最初の要素のデストラクタのみが呼び出され、「メモリの割り当て」によって返されたアドレスとは異なるアドレスで「メモリの解放」が呼び出されます。腐敗。

しかし、あちこちで、deleteafterを使用new[]するとメモリ リークが発生するという誤ったステートメントを読むことができます。デストラクタが最初の要素に対してのみ呼び出され、おそらく呼び出されなかったデストラクタがヒープに割り当てられたサブオブジェクトを解放しなかったという事実よりも、ヒープ破損のサイズが重要であると思います。

deleteafterを使用new[]すると、一部の C++ 実装でメモリ リークが発生する可能性があります。

4

10 に答える 10

30

私が C++ コンパイラであり、メモリ管理を次のように実装するとします。予約済みメモリのすべてのブロックの先頭に、メモリのサイズ (バイト単位) を追加します。このようなもの;

| size | data ... |
         ^
         pointer returned by new and new[]

newメモリ割り当てに関しては、 と の間に違いはないことに注意してくださいnew[]。どちらも、特定のサイズのメモリ ブロックを割り当てるだけです。

delete[]正しい数のデストラクタを呼び出すために、配列のサイズをどのように知るのでしょうか? sizeメモリ ブロックの を で割るだけです。sizeof(T)ここTで、 は配列の要素の型です。

deleteここで、単純にデストラクタを 1 回呼び出してからバイトを解放するように実装するsizeと、後続の要素のデストラクタが呼び出されることはありません。これにより、後続の要素によって割り当てられたリソースがリークします。それでも、(バイトではなく) バイトを解放するため、ヒープの破損は発生しませんsizesizeof(T)

于 2009-12-16T09:24:52.130 に答える
14

ミキシングnew[]deleteメモリ リークの原因とされるものについてのおとぎ話は、まさにおとぎ話です。現実にはまったく根拠がありません。どこから来たのかはわかりませんが、今では独自の生命を獲得し、ウイルスのように生き残り、初心者から初心者へと口コミで広まっています。

この「メモリ リーク」のナンセンスの背後にある最も可能性の高い理論的根拠は、無邪気で単純な観点からすると、deleteとの違いdelete[]delete1 つのオブジェクトのみを破棄するために使用され、delete[]はオブジェクトの配列 (「多数の」オブジェクト) を破棄することです。このことから通常導き出される単純な結論は、配列の最初の要素が によって破棄されdelete、残りの要素は存続するため、「メモリ リーク」が発生するというものです。もちろん、典型的なヒープの実装について少なくとも基本的な知識を持っているプログラマーであれば、その最も可能性の高い結果は「メモリ リーク」ではなく、ヒープの破損であることをすぐに理解できます。

単純な「メモリ リーク」理論のもう 1 つの一般的な説明は、間違った数のデストラクタが呼び出されるため、配列内のオブジェクトが所有するセカンダリ メモリの割り当てが解除されないというものです。これは正しいかもしれませんが、明らかに非常に強引な説明であり、ヒープの破損に関するより深刻な問題に直面した場合には、ほとんど意味がありません。

要するに、異なる割り当て関数を混在させることは、確実で予測不可能で非常に実用的な未定義の動作につながるエラーの 1 つです。この定義されていない動作の兆候に具体的な制限を課そうとする試みは、時間の無駄であり、基本的な理解が欠如していることを示しています。

Needless to add, new/delete and new[]/delete[] are in fact two independent memory management mechanisms, which are independently customizable. Once they get customized (by replacing raw memory management functions) there's absolutely no way to even begin to predict what might happen if they get mixed.

于 2009-12-19T02:12:10.980 に答える
7

あなたの質問は本当に「なぜヒープの破損が起こらないのですか?」のようです。その答えは、「ヒープマネージャーが割り当てられたブロックサイズを追跡するため」です。少し C に戻りましょう: C で単一の int を割り当てint* p = malloc(sizeof(int))たい場合は、 を行います。サイズの配列を割り当てたい場合は、またはnと書くことができます。しかし、いずれにせよ、あなたはそれを解放しますint* p = malloc(n*sizeof(int))int* p = calloc(n, sizeof(int))free(p)、どのように割り当てても。free() にサイズを渡すことはありません。free() は解放する量を「知っている」だけです。これは、malloc() で処理されたブロックのサイズがブロックの「前」のどこかに保存されるためです。C++ に戻ると、new/delete と new[]/delete[] は通常、malloc の観点から実装されます (そうである必要はありませんが、それに依存するべきではありません)。これが、new[]/delete の組み合わせがヒープを破損しない理由です。delete は適切な量のメモリを解放しますが、私の前に誰もが説明したように、適切な数のデストラクタを呼び出さないとリークが発生する可能性があります。

とはいえ、C++ で未定義の動作について推論することは、常に無意味な作業です。new[]/delete の組み合わせがたまたま機能したり、「のみ」リークしたり、ヒープの破損を引き起こしたりすることが問題になるのはなぜですか? そのようにコーディングするべきではありません。そして、実際には、可能な限り手動でのメモリ管理は避けます。STL とブーストには理由があります。

于 2009-12-16T10:21:25.970 に答える
4

配列の最初の要素以外のすべてに対して呼び出されるわけではない重要なデストラクタが一部のメモリを解放することになっている場合、これらのオブジェクトが適切にクリーンアップされないため、メモリ リークが発生します。

于 2009-12-16T09:19:49.060 に答える
3

返事が遅くなりましたが…

削除メカニズムが単純にデストラクタを呼び出し、解放されたポインタを sizeof によって暗示されるサイズとともにフリー スタックに置く場合、 new[] で割り当てられたメモリのチャンクで delete を呼び出すと、メモリが失われますが、腐敗ではありません。より洗練された malloc 構造は、この動作を破損または検出する可能性があります。

于 2010-03-16T10:09:49.870 に答える
3

未定義の動作が発生することは別として、リークの最も直接的な原因は、配列内の最初のオブジェクト以外のすべてに対してデストラクタを呼び出さない実装にあります。オブジェクトにリソースが割り当てられている場合、明らかにリークが発生します。

これは、この動作をもたらすと考えることができる最も単純なクラスです。

 struct A { 
       char* ch;
       A(): ch( new char ){}
       ~A(){ delete ch; }
    };

A* as = new A[10]; // ten times the A::ch pointer is allocated

delete as; // only one of the A::ch pointers is freed.

PS: コンストラクターは、他の多くのプログラミングの間違いでも呼び出されないことに注意してください: 非仮想基底クラスのデストラクタ、スマート ポインターへの誤った依存など。

于 2009-12-16T09:20:51.093 に答える
3

デストラクタが呼び出されないため、デストラクタがメモリを解放する場合は常に、C++ のすべての実装でリークが発生します。

場合によっては、さらに悪いエラーが発生する可能性があります。

于 2009-12-16T09:21:34.070 に答える
3

new() 演算子がオーバーライドされていて、new[] がオーバーライドされていない場合、メモリ リークが発生する可能性があります。同じことが delete / delete[] 演算子にも当てはまります

于 2009-12-16T09:25:48.643 に答える
2

両方の原因であると答えられないのはなぜですか?

明らかに、ヒープの破損が発生するかどうかにかかわらず、メモリ リークが発生します。

というか、newとdeleteを再実装できるので……何も起こらないのではないか。技術的には、new と delete に new[] と delete[] を実行させることができます。

したがって: 未定義の動作。

于 2011-02-28T16:10:30.960 に答える
1

重複としてマークされた質問に答えていたので、一致する場合に備えてここにコピーします。メモリ割り当ての仕組みについては以前からよく言われていましたが、その原因と結果について説明します。

グーグルからちょっとしたこと:http://en.cppreference.com/w/cpp/memory/new/operator_delete

とにかく、deleteは 1 つのオブジェクトに対する関数です。インスタンスをポインターから解放し、終了します。

delete[]は、配列の割り当てを解除するために使用される関数です。つまり、ポインターを解放するだけではありません。その配列のメモリブロック全体をガベージとして宣言します。

それは実際にはすべてクールですが、アプリケーションが機能することを教えてくれます。あなたはおそらく疑問に思っています...なぜですか?

解決策は、C++ ではメモリ リークが修正されないことです。括弧なしで delete を使用すると、配列だけがオブジェクトとして削除されます。このプロセスはメモリ リークを引き起こす可能性があります。

クールな話、メモリ リーク、なぜ気にする必要があるのですか?

割り当てられたメモリが削除されない場合、メモリ リークが発生します。そのメモリは不必要なディスク領域を必要とし、ほとんど理由もなく有用なメモリを失うことになります。これは悪いプログラミングであり、おそらくシステムで修正する必要があります。

于 2015-02-04T21:17:42.070 に答える