【回転】C++のnew/deleteとnew[]/delete[]について

13029 ワード

【転載】浅談C++のnew/deleteとnew[]/delete[]原作者の共有に感謝(侵入)
C++では、newやdeleteを使ってメモリを動的に申請したり解放したりすることが多いかもしれませんが、以下の問題を考えたことがありますか?
newとdeleteは関数ですか?
新[]とdelete[]は何ですか?いつ使いますか?
operator newとoperator deleteを知っていますか?
なぜnew[]から出た配列はdeleteで解放できる場合もあればだめなのか.

もしあなたがこれらの問題に疑問を持っているなら、私のこの文章を見てみてください.
newとdeleteはいったい何ですか?
仕事を探している同級生が面接の本を読むと、sizeofが関数ではないことを証明する理由がたくさんあると信じています.ここではsizeofと同様にnewやdeleteも関数ではなく、C++で定義されたキーワードであり、特定の構文で式を構成することができます.sizeofとは異なり、sizeofはコンパイル時にその戻り値を決定することができ、newとdeleteの背後にあるメカニズムは複雑である.下に進む前に、newは何をすべきだと思いますか?最初の反応は、newはC言語のmalloc関数と同じではないでしょうか.空間を動的に申請するために使われています.半分正解しました.次の文を見てください.
string *ps = new string("hello world");

newとmallocは少し違いますが、mallocはスペースを申請した後、メモリを必要としないで、newはできます.だからnew expressionの背後でやるべきことはあなたが想像していたほど簡単ではありません.newの背後にあるメカニズムを例で説明する前に、operator newoperator deleteが何なのかを知る必要があります.
operator newとoperator delete
この2つは実はC++言語標準ライブラリのライブラリ関数で、原型はそれぞれ以下の通りです.
void *operator new(size_t);     //allocate an object
void *operator delete(void *);    //free an object

void *operator new[](size_t);     //allocate an array
void *operator delete[](void *);    //free an array

後ろの2つは先に見なくてもいいです.後で紹介します.前の2つはいずれもC++標準ライブラリ関数ですが、これは関数だと思いますか?疑わないでください.これが関数です.C++Primerという本では、newとdeleteの式(例えばoperator=はリロード=オペレータ)ではないと書かれています.newとdeleteはリロードを許さないからです.しかし、なぜoperator newとoperator deleteで命名したのかまだ分かりません.分かりにくいです.私たちはそれらの意味を知っていればいいのですが、この2つの関数はC言語のmallocとfree関数に似ていて、メモリを申請して解放するために使用されています.operator newはメモリを申請した後、メモリを初期化せずに、申請メモリのポインタに直接戻ります.
私たちは直接私たちのプログラムでこれらの関数を使用することができます.
newとdeleteの背後にあるメカニズム
上記の2つの関数を知った後、newとdeleteの背後にあるメカニズムを1つの例で説明します.
単純なC++組み込みタイプではなく、複雑なクラスタイプを使用してクラスAを定義します.
class A
{
public:
    A(int v) : var(v)
    {
        fopen_s(&file, "test", "r");
    }
    ~A()
    {
        fclose(file);
    }

private:
    int var;
    FILE *file;
};

簡単です.クラスAには2つのプライベートメンバーがあり、1つのコンストラクション関数と1つのコンストラクション関数があり、コンストラクション関数でプライベート変数varを初期化し、1つのファイルを開き、コンストラクション関数で開いているファイルを閉じます.
我々は
class *pA = new A(10);

をクリックして、クラスのオブジェクトを作成し、ポインタpAを返します.次の図に示すnewの背後で完了した作業:
簡単にまとめます.
まず、上記のoperator new標準ライブラリ関数を呼び出す必要があります.入力されたパラメータはclass Aのサイズで、ここでは8バイトです.なぜ8バイトなのかは、「C++オブジェクトモデルに深く入り込む」という本を読んでください.ここでは説明しません.このように関数は、メモリ割り当ての開始アドレスを返し、ここでは0 x 007 da 290と仮定する.
上に割り当てられたメモリは初期化されていないし、タイプ化されていない.第2のステップは、この元のメモリ上でクラスオブジェクトを初期化し、対応する構造関数を呼び出し、ここではA:A(10);という関数を呼び出し、図からもこの要求されたメモリを初期化したことがわかります.var=10, file .
最後のステップは、新しく割り当てられて構築されたオブジェクトのポインタを返します.ここで、pAは0 x 007 da 290というメモリを指し、pAのタイプはクラスAオブジェクトのポインタです.
この3つのステップは、逆アセンブリで対応するアセンブリコードを見つけることができます.ここではリストしません.
ではdeleteは何をしましたか?それとも上の例に続いて、このとき申請したクラスの対象を解放したい場合はどうしますか?もちろん、次の文を使用して実行できます.
delete pA;

deleteがやったことは次の図のようになります.
deleteは2つのことをしました
pAがオブジェクトを指す構造関数を呼び出し、開いているファイルを閉じます.
上記の標準ライブラリ関数operator deleteによってオブジェクトのメモリが解放され、入力関数のパラメータはpAの値、すなわち0 x 007 d 290である.
はい、newとdeleteの背後でやったことを説明しましたが、簡単だと思いますか?コンストラクション関数とコンストラクション関数の呼び出しが1つ増えたのではないでしょうか.
配列を申請して解放するにはどうすればいいですか?
動的に配列を割り当てるのによく使われますが、そうかもしれません.
string *psa = new string[10];      //array of 10 empty strings
int *pia = new int[10];           //array of 10 uninitialized ints

上記の配列を申請する際にnew []という式を使用して完了しました.上記のnewとdeleteの知識によると、最初の配列はstringタイプで、保存オブジェクトのメモリ空間を割り当てた後、stringタイプのデフォルト構造関数を呼び出して配列内の各要素を順次初期化します.2つ目は,intオブジェクトを10個格納するメモリ領域を割り当てた組み込み型の配列を申請したが,初期化しなかった.
スペースを解放したい場合は、次の2つの文を使用します.
delete [] psa;
delete [] pia;

すべてdelete []式を使って、この地方の[]が普通の情況の下で漏らすことができないことに注意します!この2つの文がそれぞれ何をしているのか想像することもできます.最初のstringオブジェクトに対してそれぞれ構造関数を呼び出し、オブジェクトに割り当てられたすべてのメモリ領域を解放します.2つ目は、組み込みタイプに構造関数が存在しないため、10個のint型に割り当てられたすべてのメモリ領域を直接解放します.
ここでは、psaがオブジェクトを指す配列の大きさをどのように知るかという問題があります.どのようにして何回の構造関数を呼び出すか知っていますか?
この問題は直接new[]のオブジェクト配列を保存する必要がある場合、配列の次元を保存する必要があります.C++の方法は、配列空間を割り当てるときに4バイトのサイズを多く割り当て、配列のサイズを専門に保存することです.delete[]のときにこの保存された数を取り出すことができ、構造関数を呼び出す必要があることが何回か分かります.
やはり図で説明したほうが明らかで、クラスAを定義したが、クラスの内容を具体的に説明しない.このクラスには表示される構造関数、構造関数などがある.では呼び出すと
class A *pAa = new A[3];

時にしなければならないことは以下の通りです.
この図から、申請時に配列オブジェクトの上に配列のサイズを保存するために4バイトも割り当てられていることがわかりますが、最終的には、すべての割り当て空間の開始アドレスではなく、オブジェクト配列のポインタが返されます.
これで解放は簡単です.
delete [] pAa;

ここで注意すべき点は、
構造関数を呼び出す回数は、配列オブジェクトポインタの前の4バイトから取り出す. operator delete[]関数に入力されるパラメータは、配列オブジェクトのポインタpAaではなく、pAaの値が4減少する.
なぜnew/delete、new[]/delete[]をペアで使用するのですか?
実はこんなにたくさん言って、まだ私がこの文章を書く最も原始的な意図に着いていません.上から説明したように、new/delete、new[]/delete[]の動作原理がわかりました.それらの間に違いがあるので、ペアで使用する必要があります.しかし、あいにく問題はそんなに簡単ではありません.これも私が直面した問題です.次のコードです.
int *pia = new int[10];
delete []pia;

これは間違いなく大丈夫ですが、delete []pia;delete pia;に変えると問題になりますか?
これは前節で述べなかった問題にかかわる.new []で4バイトを多く割り当てる理由について述べました.配列のサイズを知る必要があるからですが、構造関数(内蔵タイプ、ここのint配列など)を呼び出さないと?new []ではその4バイトを多く割り当てる必要はありません.delete[]では、int配列に割り当てられた空間を2ステップ目に直接解放します.ここでdelete pia;を使用すると、operator delete関数が呼び出され、入力されたパラメータは配列に割り当てられた開始アドレスであり、このメモリ空間を解放することである.問題はありません.
ここで、new []を使用してdeleteでオブジェクトを解放する前に、オブジェクトのタイプは内蔵タイプまたはカスタム構造関数のないクラスタイプです.
カスタム構造関数を持つクラスタイプであれば、new []でクラスオブジェクト配列を作成し、deleteで解放すると何が起こるかを見てみましょう.上の例で説明します.
class A *pAa = new class A[3];
delete pAa;

ではdelete pAa;は2つのことをしました
一度pAaが指すオブジェクトの構造関数を呼び出す. operator delete(pAa);を呼び出してメモリを解放します.
明らかに、ここでは配列の最初のクラスオブジェクトに対してのみ構造関数が呼び出され、後ろの2つのオブジェクトには構造関数が呼び出されていません.クラスオブジェクトに大量のメモリが申請された場合、構造関数から解放する必要がありますが、配列オブジェクトを破棄するときに構造関数が呼び出されないと、メモリが漏洩します.
上の質問は大丈夫だと言ったら、2つ目は致命的です!直接pAaの指すメモリの空間を解放して、これはいつも深刻なセグメントの誤りをもたらして、プログラムは必然的に潰れます!割り当てられた空間の先頭アドレスはpAaが指す場所から4バイトを減算した場所であるからである.パラメータをそのアドレスに設定する必要があります!
同様に、newを使用して割り当てると、delete []で解放されるとどのような問題が発生するかを分析することができます.プログラムエラーが発生するのではないでしょうか.
総じて言えば、new/delete、new[]/delete[]を組み合わせて使うのは間違いありません.
参考資料:
C++Primer第4版