[C ++] newおよびdelete演算子に関するすべてを知っていますか?

こんにちは 以下では、すべての人によく知られているnewおよびdelete演算子について、より正確には本(少なくとも初心者向けの本)には書かれていないものについて説明します。

この記事を書くために、私はnewdeleteについてよくある誤解に促されました。これはフォーラムや一部の本(!!!)でもよく見られます。

私たちは皆、 新規および削除とは何かを知っていますか? それとも私たちが知っていると思う?

この記事は、あなたがそれを理解するのに役立ちます(そして、知っている人は批判することができます:))



:以下の説明は、new演算子の他の形式およびdelete演算子のすべての形式について、new演算子のみに専念します。以下で説明することもすべて当てはまり、類推によって適用できます。



それで、初心者が新しいことを説明するときに通常本に書かれているものから始めましょう(テキストは「天井から」取られますが、一般的には真実に対応します):

new演算子は、必要なサイズ以上のメモリを割り当て、C言語関数とは異なり、メモリが割り当てられているオブジェクトのコンストラクターを呼び出します...ニーズに合わせ新しい演算子をオーバーロードできます(どこかに実装するために書き込みます)。


そして、例えば、彼らは新しい演算子のプリミティブなオーバーロード(実装)を示し、そのプロトタイプは次のようになります

void* operator new (std::size_t size) throw (std::bad_alloc);







私が注意したいこと:

1. C ++言語の新しい キーワード新しい演算子を、1つのエンティティとして話されるすべての場所で共有することはありません。

2.作成するすべての場所で、 newがオブジェクトのコンストラクターを呼び出します。

最初と2番目の両方は、一般的な誤解です。



しかし、初心者向けの本を望んでいません。標準、つまりセクション5.3.4と18.6.1に目を向けます。このセクションでは、この記事のトピックが実際に明らかにされています(むしろ公開されています)。

5.3.4

new-expressionは、適用されるtype-id(8.1)またはnew-type-idのオブジェクトを作成しようとします。 / *さらに興味がない* /

18.6.1

void * operator new(std :: size_t size)throw(std :: bad_alloc);

効果:新しい式(5.3.4)によって呼び出され、次のサイズのバイトを割り当てる割り当て関数

そのサイズのオブジェクトを表すために適切に位置合わせされたストレージ/ *これ以上興味はありません* /




ここでは、最初のケースではnewexpressionとして参照され、2番目のケースではoperatorとして宣言されていることがすでにわかりました そして、これらは実際には2つの異なるエンティティです!

これがなぜそうなのかを考えてみましょう。そのためには、 newを使用してコードをコンパイルした後に取得したアセンブラーリストが必要になります。 さて、今ではすべてについて順番に。



new-expressionは、 ifwhileなどと同じ言語ステートメントです 。 (ただし while 、などはまだstatementと呼ばれていますが、歌詞は破棄します)i.e. リストでそれを満たしている場合、コンパイラはこのステートメントに対応する特定のコードを生成します。 また、C ++言語のキーワードの 1つが新しくif 's、 for'などとの共通性が確認されています。 そして、 演算子new()は、同じ名前のC ++言語関数であり、その動作は再定義できます。 重要-operator new()は、メモリが割り当てられるオブジェクトのコンストラクターを呼び出しません 。 適切なサイズのメモリを割り当てるだけです。 関数関数との違いは、例外をスローしてオーバーライドできることと、特定のクラスに対して演算子を作成できることです。これにより、このクラスに対してのみオーバーライドされます(残りは自分で覚えてください:))。

ただし、 new-expressionはオブジェクトのコンストラクターを呼び出すだけです。 何も呼び出さず、単にそれを満たしていると言う方が正確ですが、コンパイラはコンストラクタを呼び出すためのコードを生成します。



図を完成させるために、次の例を検討してください。



 #include <iostream> class Foo { public: Foo() { std::cout << "Foo()" << std::endl; } }; int main () { Foo *bar = new Foo; }
      
      







このコードの実行後、予想どおり、「Foo()」が出力されます。 理由を理解しましょう。このためには、アセンブラーを調べる必要がありますが、便宜上、少しコメントしました。

(コードはMSVS 2012で使用されているclコンパイラによって取得されましたが、主にgccを使用していますが、これは適用されません)

 /Foo *bar = new Foo; push 1 ;      Foo call operator new (02013D4h) ;  operator new pop ecx mov dword ptr [ebp-0E0h],eax ;  ,   new,  bar and dword ptr [ebp-4],0 cmp dword ptr [ebp-0E0h],0 ;   0    bar je main+69h (0204990h) ;  0,    (   main   - ,    ) mov ecx,dword ptr [ebp-0E0h] ;       ecx (MSVS   this  ecx(rcx)) call Foo::Foo (02011DBh) ;    ;   
      
      





何も理解していない人のために、ここに一口のような擬似コードで起こったことの(ほぼ)類似物があります(つまり、これをコンパイルしようとしないでください:))

 Foo *bar = operator new (1); //  1 -   bar->Foo(); //  
      
      







上記のコードは、上記のすべてを確認します。

1.(言語) operator newoperator new()は同じものではありません。

2. operator new()はコンストラクターを呼び出しません

3.コンパイラーがコンストラクターへの呼び出しを生成し、コード内のキーワード「new」を満たします



結論:この記事が、 new-expressionoperator new()の違いを理解するのに役立つこと、または誰かが知らない場合でも存在すること(この違い)がわかることを願っています。



PS delete 演算子operator delete()には同様の違いがあるため、記事の冒頭で説明しませんでした。 なぜその説明が意味をなさないのか理解できたと思いますし、 削除のために上に書かれたものの正確性を独立して検証することができます。



更新:

ニックネームがkhimで個人的に通信するKhrazhitelは、上記の本質をよく示している次のコードを提案しました。

 #include <iostream> class Test { public: Test() { std::cout << "Test::Test()" << std::endl; } void* operator new (std::size_t size) throw (std::bad_alloc) { std::cout << "Test::operator new(" << size << ")" << std::endl; return ::operator new(size); } }; int main() { Test *t = new Test(); void *p = Test::operator new(100); // 100     }
      
      





このコードは次を出力します

 Test::operator new(1) Test::Test() Test::operator new(100)
      
      





予想されることです。



All Articles