C++メモリ割り当てメカニズムの詳細(コード付)


文書ディレクトリ
  • メモリ割り当てプロセス:
  • 割り当てメモリ
  • メモリを解放
  • メモリ管理
  • 例1
  • 例2
  • 例3



  • メモリ割り当てのプロセス:
    メモリの割り当て
    1.new()newは、operator new()を呼び出すことによって空間の割り当てを完了し、parameter newによって割り当てられた空間にオブジェクトを作成するオブジェクトを作成するための関数です.2.operator new()グローバル関数.割り当てられたメモリを指すvoid*型のポインタを返します.operator new()はmallocを呼び出してメモリを取得します.3.parameter newはnew(pointer)className(parameter)として使用されます.この方法には空間が割り当てられていません.pointerパラメータは割り当てられたメモリを指し、pointerが指すメモリにオブジェクトを作成するコンストラクション関数(パラメータまたはパラメータなし)を呼び出します.
    このプロセスから,メモリ割り当ては最終的にmallocによって行われ,構造関数はparameter newを呼び出すことによって呼び出されることが分かる.
    呼び出しプロセスの理解を深めるには、実際にオブジェクトを作成することによって、オブジェクトを作成するときに一般的に次のフォーマットを使用します.
    Foo* f = new Foo(3, "asdf");
    

    この文はnewメソッドによってオブジェクトを取得します.newメソッドには、newの代わりに次のアクションが含まれます(すなわち、newの代わりに次のアクションを使用してオブジェクトを作成できます).
    	//1  operator new          ,       void*
    	//operator new       ,    malloc     
    	void* p = operator new(sizeof(Foo));
    	//2    void*            Foo*  
    	Foo* f = (Foo*)p;
    	//3  new(  ) structureFunction()           
    	new(f)Foo(2, "qwe");
    

    ここでoperator newプロシージャはmallocを呼び出し、mallocはsizeof(Foo)を介して適切なサイズの空間を割り当て、アドレスを返します.
    もちろんnewのほかにもnew[]があります.operator newで空間を取得し、parameter newで空間にオブジェクトを作成するという数回の操作が行われます.注目すべきは、new[]で得られた空間には、delete[]を呼び出すと、呼び出す数の構造関数をcookieで知ることができるcookieという空間(それぞれ割り当てられた空間の先頭に位置する)が2つ追加されていることである.
    メモリの解放
    1 delete deleteプロセスでは、まず構造関数を呼び出してオブジェクトが破棄される前の作業を完了します.たとえば、オブジェクトが指す空間を破棄し、operator deleteを呼び出してオブジェクト自体の空間を破棄する必要があります.2 operator delete()グローバル関数、operator deleteはfree完了空間解放を呼び出す
    同様に、呼び出しプロセスの理解を深めるには、オブジェクトを実際に破棄します.
    オブジェクトを破棄する場合は、一般的に次の形式を使用します.
    delete f;
    

    この文はdeleteメソッドによってオブジェクトの破棄を完了し、deleteの代わりに次の2つのステップに分けられます(つまり、deleteの代わりに次の操作でオブジェクトを破棄できます).
    f->~Foo();
    //	//2  free          
    operator delete(f); 
    

    まず,構造関数を呼び出してオブジェクトの破棄を完了する前の後始末を行い,operator deleteを呼び出して自分の空間を解放する.
    一方、new[]を使用して作成されたオブジェクトについては、delete[]を使用します.次のようにします.
    Foo * f3 = new Foo[4];
    delete[] f3;
    

    その中のdelete[]はdeleteを複数回呼び出して配列オブジェクトの破棄プロセスを完了しますが、いったい何回呼び出しますか?これがクッキーの存在の意味であり、前述したように、クッキーはnew[]でオブジェクトを作成するときに、配列オブジェクトのサイズを識別するために追加されたメモリ領域であり、クッキーはdelete[]を助け、割り当てられた空き領域を正しく解放するためにdeleteを何回呼び出す必要があるかを教えてくれます.ここでよく言及されるのは、new[]を使用して作成されたオブジェクトに対してdelete[]を使用して破棄する必要があります.そうしないと、メモリ漏洩のトラブルが発生する可能性があります.言うべきことは、面倒なのは、オブジェクトのメモリが漏れるかどうか(オブジェクト自体のメモリの破棄にクッキーヘルプがあるため)ではなく、配列オブジェクトのオブジェクトのポインタが指す空間であり、delete[]を使用すると、構造関数が複数回呼び出され、構造関数はオブジェクト(配列オブジェクトの各オブジェクト)のポインタが指す空間を解放します.deleteによってnew[]が解放される場合オブジェクトは、deleteが1回の解析関数のみを呼び出す結果、配列オブジェクトのうち1つのオブジェクトのポインタが指す空間だけが解放され、他のオブジェクトのポインタが指す空間は解放されず、配列オブジェクト自体が解放され、それらの「他のオブジェクトのポインタが指す空間」はメモリ漏れのものになります.つまり、オブジェクトにポインタ(または参照タイプ、その他のオブジェクトタイプ)がない限り、deleteでnew[]で作成したオブジェクトを解放できますか?確かに、構造関数を呼び出して後始末する必要はありませんので、メモリの漏洩は発生しませんが、世代コードを「規範化されていない」ようにし、「new[]の場合」ではなく共通のコードフォーマットを求めます.生成されたポインタのないオブジェクトにはdeleteを,new[]で生成されたポインタのあるオブジェクトにはdelete[]を用いるという状況別に,把握しにくいフォーマットを用いる.
    メモリ管理
    newのプロシージャはoperator newを呼び出して空間を取得し、parameter newを呼び出して空間にオブジェクトを作成することを知っています.これでは、newのたびにoperator newとparameter newが呼び出され、operator newのたびにmallocが呼び出されてメモリが申請されます.メモリを一括申請する方法はありますか?これにより、malloc申請メモリを毎回呼び出す必要がなくなり、parameter newを呼び出してオブジェクトを作成するだけで済みます.これは可能で、operator newを再ロードすることで実現できます.operator newを再ロードし、メモリ割り当て関数を管理します.これがメモリ管理の内容です.operator newをリロードし、メモリプールを使用すると、mallocの呼び出し回数を減らすだけでなく、クッキーを削除し、メモリの破片を減らすことができ、時間と空間の効果があります.
    インスタンス1
    メモリ管理の手順については、次の例を示します.
    #include
    #include
    using namespace std;
    
    class Screen {
         
    public:
    	Screen(int x) :i(x) {
         };
    	int get() {
          return i; }
    //operator new operator delete   static,      static
    	static void* operator new(size_t);
    	static void operator delete(void*, size_t);
    
    private:
    	Screen* next;//     
    	int i;
    private:
    	static Screen* freeStore;//       (    )
    	static const int screenChunk;
    };
    Screen* Screen::freeStore = 0;
    const int Screen :: screenChunk = 24;//        24   
    
    //  operator new  ,    new           ,   operator new    ,        size_t 
    //     ,  freeStore  ,      ,                   ,        
    void* Screen::operator new(size_t size) {
         
    	Screen* p;
    	if (!freeStore) {
         
    		//linked list    ,         
    		size_t chunk = screenChunk * size;//   24(screenChunk) 
    		 //       
    		freeStore = p = reinterpret_cast<Screen*>(new char[chunk]);
    		//          ,      linked list     
    		//  p   Screen  ,   1     Screen    
    		for (; p != &freeStore[screenChunk - 1]; ++p)
    			p->next = p + 1;
    		p->next = 0; //    Screen   next   0
    	}
    	//             
    	p = freeStore;
    	freeStore = freeStore->next;
    	return p;
    }
    
    void Screen::operator delete(void* p, size_t) {
         
    	// deleted object   free list   
    	(static_cast<Screen*>(p))->next = freeStore;
    	freeStore = static_cast<Screen*>(p);
    }
    
    //     operator new  operator delete     8   int        ,    cookie
    //        operator new  operator delete        ,      operator new,           
    int main() {
         
    	cout << sizeof(Screen) << endl;
    
    	const int N = 100;
    	Screen* p[N];
    	for (int i = 0; i < N; i++)
    		p[i] = new Screen();
    
    	//    ,       8,     cookie
    	for (int i = 0; i < 10; i++)
    		cout << p[i] << endl;
    
    	for (int i = 0; i < N; i++)
    		delete p[i];
    
    	return 0;
    }
    

    Screenクラスでoperator newとoperator deleteを書き換えることで簡単なメモリ管理が完了し、その後newを使用してScreenオブジェクトを作成し、deleteを使用してScreenオブジェクトを破棄すると、このグローバル関数operator newとoperator deleteを呼び出す場所で上記のように書き換えたoperator newとoperator deleteが呼び出されます.mainテスト例では,出力結果における各アドレス距離は8であり,ちょうどクラスメンバーにおけるintとScreen*の大きさであり,各オブジェクトおよび前後クッキーがないことを示した(ただし,単一チェーンテーブル全体の前後にクッキー,すなわち24個の連続オブジェクトの連続メモリの前後にクッキーがある).
    例2
    例1では,クッキーは削除したが,ポインタを用いたためメモリの有効利用率が低下した.次の例では、この点を改善します.
    #include 
    using namespace std;
    
    class Airplane {
         
    private :
    	//                 8bit
    	struct AirplaneRep {
         
    		unsigned long miles;//4bit
    		char type;//1bit 
    	};
    private:
    //  union ,          ,    Airplane*,      ,     AirplaneRep 
    	union {
         
    		AirplaneRep rep;
    		Airplane* next; 
    	};
    public://      
    	unsigned long getMiles() {
          return rep.miles; }
    	char getType() {
          return rep.type; }
    	void set(unsigned long m, char t) {
         
    		rep.miles = m; rep.type = t;
    	}
    public:
    	static void* operator new(size_t size);
    	static void operator delete(void* deadObject, size_t size);
    private:
    	static const int BLOCK_SIZE;
    	static Airplane* headOfFreeList;
    };
    Airplane* Airplane::headOfFreeList;
    const int Airplane::BLOCK_SIZE = 512;
    
    void* Airplane:: operator new(size_t size) {
         
    	//      ,    operator new。          
    	if (size != sizeof(Airplane))
    		return ::operator new(size);
    
    	Airplane* p = headOfFreeList;
    	//  p  ,  list      
    	if (p)
    		headOfFreeList = p->next;
    	else {
         
    		//free list   ,       ,      operator new   
    	Airplane* newBlock = static_cast<Airplane*>(::operator new(BLOCK_SIZE * sizeof(Airplane)));
    
    		//        free list
    		//    #0,          。
    	for (int i = 1; i < BLOCK_SIZE - 1; ++i)
    		newBlock[i].next = &newBlock[i + 1];
    	newBlock[BLOCK_SIZE - 1].next = 0;//       
    	//p  #0
    	p = newBlock;
    	headOfFreeList = &newBlock[1];
    	}
    	return p;
    }
    
    void Airplane::operator delete(void* deadObject, size_t size) {
         
    	if (deadObject == 0)return;
    	if (size != sizeof(Airplane)) {
         
    		::operator delete(deadObject);
    		return;
    	}
    
    	Airplane* carcass = static_cast<Airplane*>(deadObject);
    	carcass->next = headOfFreeList;
    	headOfFreeList = carcass; 
    }
    
    int main() {
         
    	cout << sizeof(Airplane) << endl;
    
    	const int N = 100;
    	Airplane* p[N];
    	for (int i = 0; i < N; i++)
    		p[i] = new Airplane();
    
    	//    ,       8,     cookie
    	for (int i = 0; i < 10; i++)
    		cout << p[i] << endl;
    
    	for (int i = 0; i < N; i++)
    		delete p[i];
    
    	return 0;
    }
    

    主にunionを使用することで、nextとオブジェクトをそれぞれunionの1つとして改善し、アイドルキューと使用時に同じメモリが異なる意味を持つようにしました.他の部分は基本的に同じですが、割り当て空間はインスタンス1のnewからグローバルoperator newに変更されます.
    例3
    実際の運用では、各classでoperator newとoperator deleteを書き換えるべきではありません.そのため、メモリ管理に特化したクラスを作成することができます.メモリ管理が必要なクラスでは、クラスの静的メンバーを定義するだけでいいです.次のようになります.
    #include
    
    using namespace std;
    
    class allocator {
         
    private :
    	struct obj {
         
    		struct obj* next;
    	};
    public:
    	void* allocate(size_t);
    	void deallocate(void*, size_t);
    private :
    	obj* freeStore = nullptr;
    	const int CHUNK = 5;
    };
    
    //::allocator           allocator
    void* ::allocator::allocate(size_t size) {
         
    	obj* p;
    	if (!freeStore) {
         
    		// linked list   
    		size_t chunk = CHUNK * size;
    		freeStore = p = (obj*)malloc(chunk);
    
    		//         listed list
    		for (int i = 0; i < (CHUNK - 1); ++i) {
         
    			p->next = (obj*)((char*)p + size);
    			p = p->next;
    		}
    		p->next = nullptr;
    	}
    	p = freeStore;
    	freeStore = freeStore->next;
    	return p;
    }
    void ::allocator::deallocate(void* p, size_t) {
         
    	// p   free list    
    	((obj*)p)->next = freeStore;
    	freeStore = (obj*)p;
    }
    
    
    //          
    class Foo {
         
    public:
    	long L;
    	string str;
    	static ::allocator myAlloc;
    public:
    	Foo() {
         }
    	Foo(long l) :L(l) {
         }
    
    	static void* operator new (size_t size) {
         
    		return myAlloc.allocate(size);
    	}
    	static void operator delete(void* pdead, size_t size) {
         
    		return myAlloc.deallocate(pdead, size);
    	}
    };
    ::allocator Foo::myAlloc;
    
    //       ,            
    int main() {
         
    	cout << sizeof(Foo) << endl;
    
    	const int N = 100;
    	Foo* p[N];
    	for (int i = 0; i < N; i++)
    		p[i] = new Foo();
    
    	//    ,       8,     cookie
    	for (int i = 0; i < 20; i++)
    		cout << p[i] << endl;
    
    	for (int i = 0; i < N; i++)
    		delete p[i];
    
    	return 0;
    }
    

    このコードでは、メモリを管理するクラスとしてallocatorを使用していますが、実際の運用では、Fooクラスのようにこのallocatorを使用すればよいのです.標準ライブラリにはすでにallocatorクラスがあるので、自分のallocatorを使用する場合は、前に::を付ける必要があります.