Skip to content

C++内存管理

一、四种内存分配和释放方法

在编程时可以通过上图的几种方法直接或间接地操作内存。下面将介绍四种C++内存操作方法:

通常可以使用malloc和new来分配内存,当然也可以使用::operator new()和分配器allocator来操作内存,下面将具体介绍这些函数的使用方法。对于不同的编译器,其allocate函数的接口也有所不同:

对于GNU C,不同版本又有所不同:

这张图中的 __gnu_cxx::__pool_alloc<T>().allocate() 对应于上张图中的 allocator<T>().allocate()

通过malloc和new分配内存、通过free和delete释放内存是十分常用的,通过::operator new操作内存比较少见,allocator分配器操作内存在STL源码中使用较多,对于不同的编译环境使用也有所不同。下面这个例子是基与VS2013环境做测试的:

#include <iostream>
#include <complex>
#include <memory>				 //std::allocator  
//#include <ext\pool_allocator.h>	 //GCC使用,欲使用 std::allocator 以外的 allocator, 就得自行 #include <ext/...> 
using namespace std;
namespace jj01
{
	void test_primitives()
	{
		cout << "\ntest_primitives().......... \n";

		void* p1 = malloc(512);	//512 bytes
		free(p1);

		complex<int>* p2 = new complex<int>; //one object
		delete p2;

		void* p3 = ::operator new(512); //512 bytes
		::operator delete(p3);

		//以下使用 C++ 標準庫提供的 allocators。
		//其接口雖有標準規格,但實現廠商並未完全遵守;下面三者形式略異。
#ifdef _MSC_VER
		//以下兩函數都是 non-static,定要通過 object 調用。以下分配 3 個 ints.
		int* p4 = allocator<int>().allocate(3, (int*)0);
		p4[0] = 666;
		p4[1] = 999;
		p4[2] = 888;
		cout << "p4[0] = " << p4[0] << endl;
		cout << "p4[1] = " << p4[1] << endl;
		cout << "p4[2] = " << p4[2] << endl;
		allocator<int>().deallocate(p4, 3);
#endif
#ifdef __BORLANDC__
		//以下兩函數都是 non-static,定要通過 object 調用。以下分配 5 個 ints.
		int* p4 = allocator<int>().allocate(5);
		allocator<int>().deallocate(p4, 5);
#endif
#ifdef __GNUC__
		//以下兩函數都是 static,可通過全名調用之。以下分配 512 bytes.
		//void* p4 = alloc::allocate(512); 
		//alloc::deallocate(p4,512);   

		//以下兩函數都是 non-static,定要通過 object 調用。以下分配 7 個 ints.    
		void* p4 = allocator<int>().allocate(7);
		allocator<int>().deallocate((int*)p4, 7);

		//以下兩函數都是 non-static,定要通過 object 調用。以下分配 9 個 ints.	
		void* p5 = __gnu_cxx::__pool_alloc<int>().allocate(9);
		__gnu_cxx::__pool_alloc<int>().deallocate((int*)p5, 9);
#endif
	}
} //namespace

int main(void)
{
	jj01::test_primitives();
	return 0;
}

编译运行结果如下:

可见 int* p4 = allocator<int>().allocate(3, (int*)0) 操作成功申请了三个 int 的空间。

二、基本构件之 new/delete expression

1、内存申请

上面这张图揭示了new操作背后编译器做的事:

  • 1、第一步通过operator new()操作分配一个目标类型的内存大小,这里是Complex的大小;
  • 2、第二步通过static_cast将得到的内存块强制转换为目标类型指针,这里是Complex*
  • 3、第三版调用目标类型的构造方法,但是需要注意的是,直接通过pc->Complex::Complex(1, 2)这样的方法调用构造函数只有编译器可以做,用户这样做将产生错误。

值得注意的是,operator new()操作的内部是调用了malloc()函数。

2、内存释放

同样地,delete操作第一步也是调用了对象的析构函数,然后再通过operator delete()函数释放内存,本质上也是调用了free函数。

3、模拟编译器直接调用构造和析构函数

下面的代码测试环节为VS2013:

#include <iostream>
#include <string>
//#include <memory>				 //std::allocator  
using namespace std;

namespace jj02
{

	class A
	{
	public:
		int id;

		A() : id(0)      { cout << "default ctor. this=" << this << " id=" << id << endl; }
		A(int i) : id(i) { cout << "ctor. this=" << this << " id=" << id << endl; }
		~A()             { cout << "dtor. this=" << this << " id=" << id << endl; }
	};

	void test_call_ctor_directly()
	{
		cout << "\ntest_call_ctor_directly().......... \n";

		string* pstr = new string;
		cout << "str= " << *pstr << endl;
		//! pstr->string::string("jjhou");  
		//[Error] 'class std::basic_string<char>' has no member named 'string'
		//! pstr->~string();	//crash -- 其語法語意都是正確的, crash 只因為上一行被 remark 起來嘛.  
		cout << "str= " << *pstr << endl;


		//------------

		A* pA = new A(1);         	//ctor. this=000307A8 id=1
		cout << pA->id << endl;   	//1
		pA->A::A(3);
		cout << pA->id << endl;
		//!	pA->A::A(3);                //in VC6 : ctor. this=000307A8 id=3
		//in GCC : [Error] cannot call constructor 'jj02::A::A' directly

		A::A(5);
		//!	A::A(5);	  				//in VC6 : ctor. this=0013FF60 id=5
		//         dtor. this=0013FF60  	
		//in GCC : [Error] cannot call constructor 'jj02::A::A' directly
		//         [Note] for a function-style cast, remove the redundant '::A'

		cout << pA->id << endl;   	//in VC6 : 3
		//in GCC : 1  	

		delete pA;                	//dtor. this=000307A8 

		//simulate new
		void* p = ::operator new(sizeof(A));
		cout << "p=" << p << endl; 	//p=000307A8
		pA = static_cast<A*>(p);
		pA->A::A(2);
		//!	pA->A::A(2);				//in VC6 : ctor. this=000307A8 id=2
		//in GCC : [Error] cannot call constructor 'jj02::A::A' directly  	

		cout << pA->id << endl;     //in VC6 : 2
		//in GCC : 0  	

		//simulate delete
		pA->~A();					//dtor. this=000307A8 
		::operator delete(pA);		//free()
	}
} //namespace

int main(void)
{
	jj02::test_call_ctor_directly();
	return 0;
}

编译运行结果如下:

VS下可以直接通过内存空间调用构造函数,但侯杰测试在GNU C下无法通过,具体的内容可见代码注解和打印效果。

三、Array new

上图主要展示的是关于new array内存分配的大致情况。当new一个数组对象时(例如 new Complex[3]),编译器将分配一块内存,这块内存首部是关于对象内存分配的一些标记,然后下面会分配三个连续的对象内存,在使用delete释放内存时需要使用delete[]。如果不使用delete[],只是使用delete只会将分配的三块内存空间释放,但不会调用对象的析构函数,如果对象内部还使用了new指向其他空间,如果指向的该空间里的对象的析构函数没有意义,那么不会造成问题,如果有意义,那么由于该部分对象析构函数不会调用,那么将会导致内存泄漏。图中new string[3]便是一个例子,虽然str[0]、str[1]、str[2]被析构了,但只是调用了str[0]的析构函数,其他对象的析构函数不被调用,这里就会出问题。

下面将演示数组对象创建与析构过程:

#include <iostream>
#include <new>		//placement new
using namespace std;

namespace jj03
{

	class A
	{
	public:
		int id;

		A() : id(0)      { cout << "default ctor. this=" << this << " id=" << id << endl; }
		A(int i) : id(i) { cout << "ctor. this=" << this << " id=" << id << endl; }
		~A()             { cout << "dtor. this=" << this << " id=" << id << endl; }
	};

	void test_array_new_and_placement_new()
	{
		cout << "\ntest_placement_new().......... \n";

		size_t size = 3;

		{
			//case 1
			//模擬 memory pool 的作法, array new + placement new. 崩潰 

			A* buf = (A*)(new char[sizeof(A)*size]);
			A* tmp = buf;

			cout << "buf=" << buf << "  tmp=" << tmp << endl;

			for (int i = 0; i < size; ++i)
				new (tmp++) A(i);  			//3次 调用ctor 

			cout << "buf=" << buf << "  tmp=" << tmp << endl;

			//!	delete [] buf;    	//crash. why?
			//因為這其實是個 char array,看到 delete [] buf; 編譯器會企圖喚起多次 A::~A. 
			// 但 array memory layout 中找不到與 array 元素個數 (本例 3) 相關的信息, 
			// -- 整個格局都錯亂 (從我對 VC 的認識而言),於是崩潰。 
			delete buf;     	//dtor just one time, ~[0]	

			cout << "\n\n";
		}

		{
			//case 2
			//回頭測試單純的 array new

			A* buf = new A[size];  //default ctor 3 次. [0]先於[1]先於[2])
			//A必須有 default ctor, 否則 [Error] no matching function for call to 'jj02::A::A()'
			A* tmp = buf;

			cout << "buf=" << buf << "  tmp=" << tmp << endl;

			for (int i = 0; i < size; ++i)
				new (tmp++) A(i);  		//3次 ctor 

			cout << "buf=" << buf << "  tmp=" << tmp << endl;

			delete[] buf;    //dtor three times (次序逆反, [2]先於[1]先於[0])	
		}

		{
			//case 3	
			//掌握崩潰原因, 再次模擬 memory pool作法, array new + placement new. 	
			//不, 不做了, 因為 memory pool 只是供應 memory, 它並不管 construction, 
			//也不管 destruction. 它只負責回收 memory. 
			//所以它是以 void* 或 char* 取得 memory, 釋放 (刪除)的也是 void* or char*.  
			//不像本例 case 1 釋放 (刪除) 的是 A*. 
			//
			//事實上 memory pool 形式如 jj04::test 
		}

	}
} //namespace

int main(void)
{
	jj03::test_array_new_and_placement_new();
	return 0;
}

编译运行结果如下:

构造函数调用顺序是按照构建对象顺序来执行的,但是析构函数执行却相反。值得注意的是,在调用了delete的大括号代码段中,数组有三个元素,但最后只调用了第一个对象的析构函数。

接下来将更具体地展示new array对象的内存分配情况:

如果使用new分配十个内存的int,内存空间如上图所示,首先内存块会有一个头和尾,黄色部分为debug信息,灰色部分才是真正使用到的内存,蓝色部分的12bytes是为了让该内存块以16字节对齐。在这个例子中delete pi和delete[] pi效果是一样的,因为int没有析构函数。但是下面的例子就不一样了:

上图通过new申请三个Demo空间大小,内存块使用了96byte,这里是这样计算得到的:黄色部分调试信息32 + 4 = 36byte;黄色部分下面的“3”用于标记实际分配给对象内存个数,这里是三个所以里面内容为3,消耗4byte;Demo内有三个int类型成员变量,一个Demo消耗内存3 * 4 = 12byte,由于有三个Demo,所以消耗了12 * 3 = 36byte空间;到目前为止消耗36 + 4 + 36 = 76byte,加上头尾cookie一共8byte一共消耗84byte,由于需要16位对齐,所以填充蓝色部分为12byte,一共消耗了84 + 12 = 96byte。这里释放内存时需要加上delete[],上面分配内存中有个标记“3”,所以编译器将释放三个Demo对象空间,如果不加就会报错。

四、placement new

五、重载

1、C++内存分配的途径

如果是正常情况下,调用new之后走的是第二条路线,如果在类中重载了operator new(),那么走的是第一条路线,但最后还是要调用到系统的::operator new()函数,这在后续的例子中会体现。

对于GNU C,背后使用的allocate()函数最后也是调用了系统的::operator new()函数。

2、重载new 和 delete

上面这张图演示了如何重载系统的::operator new()函数,该方法最后也是模拟了系统的做法,效果和系统的方法一样,但一般不推荐重载::operator new()函数,因为它对全局有影响,如果使用不当将造成很大的问题。

如果是在类中重载operator new()方法,那么该方法有N多种形式,但必须保证函数参数列表第一个参数是size_t类型变量;对于operator delete(),第一个参数必须是void* 类型,第二个size_t是可选项,可以去掉。

对于operator new[]和operator delete[]函数的重载,和前面类似。

3、测试案例

测试一:

#include <cstddef>
#include <iostream>
#include <string>
using namespace std;

namespace jj06
{

	class Foo
	{
	public:
		int _id;
		long _data;
		string _str;

	public:
		static void* operator new(size_t size);
		static void  operator delete(void* deadObject, size_t size);
		static void* operator new[](size_t size);
		static void  operator delete[](void* deadObject, size_t size);

		Foo() : _id(0)      { cout << "default ctor. this=" << this << " id=" << _id << endl; }
		Foo(int i) : _id(i) { cout << "ctor. this=" << this << " id=" << _id << endl; }
		//virtual 
		~Foo()              { cout << "dtor. this=" << this << " id=" << _id << endl; }

		//不加 virtual dtor, sizeof = 12, new Foo[5] => operator new[]() 的 size 參數是 64, 
		//加了 virtual dtor, sizeof = 16, new Foo[5] => operator new[]() 的 size 參數是 84, 
		//上述二例,多出來的 4 可能就是個 size_t 欄位用來放置 array size. 
	};

	void* Foo::operator new(size_t size)
	{
		Foo* p = (Foo*)malloc(size);
		cout << "Foo::operator new(), size=" << size << "\t  return: " << p << endl;

		return p;
	}

	void Foo::operator delete(void* pdead, size_t size)
	{
		cout << "Foo::operator delete(), pdead= " << pdead << "  size= " << size << endl;
		free(pdead);
	}

	void* Foo::operator new[](size_t size)
	{
		Foo* p = (Foo*)malloc(size);  //crash, 問題可能出在這兒 
		cout << "Foo::operator new[](), size=" << size << "\t  return: " << p << endl;

		return p;
	}

	void Foo::operator delete[](void* pdead, size_t size)
	{
		cout << "Foo::operator delete[](), pdead= " << pdead << "  size= " << size << endl;

		free(pdead);
	}

	//-------------	
	void test_overload_operator_new_and_array_new()
	{
		cout << "\ntest_overload_operator_new_and_array_new().......... \n";

		cout << "sizeof(Foo)= " << sizeof(Foo) << endl;

		{
			Foo* p = new Foo(7);
			delete p;

			Foo* pArray = new Foo[5];	//無法給 array elements 以 initializer 
			delete[] pArray;
		}

		{
			cout << "testing global expression ::new and ::new[] \n";
			// 這會繞過 overloaded new(), delete(), new[](), delete[]() 
			// 但當然 ctor, dtor 都會被正常呼叫.  

			Foo* p = ::new Foo(7);
			::delete p;

			Foo* pArray = ::new Foo[5];
			::delete[] pArray;
		}
	}
} //namespace

int main(void)
{
	jj06::test_overload_operator_new_and_array_new();
	return 0;
}

编译运行结果如下:

测试二:

#include <vector>  //for test
#include <cstddef>
#include <iostream>
#include <string>
using namespace std;

namespace jj07
{

	class Bad { };
	class Foo
	{
	public:
		Foo() { cout << "Foo::Foo()" << endl; }
		Foo(int) {
			cout << "Foo::Foo(int)" << endl;
			// throw Bad();  
		}

		//(1) 這個就是一般的 operator new() 的重載 
		void* operator new(size_t size){
			cout << "operator new(size_t size), size= " << size << endl;
			return malloc(size);
		}

		//(2) 這個就是標準庫已經提供的 placement new() 的重載 (形式)
		//    (所以我也模擬 standard placement new 的動作, just return ptr) 
		void* operator new(size_t size, void* start){
			cout << "operator new(size_t size, void* start), size= " << size << "  start= " << start << endl;
			return start;
		}

		//(3) 這個才是嶄新的 placement new 
		void* operator new(size_t size, long extra){
			cout << "operator new(size_t size, long extra)  " << size << ' ' << extra << endl;
			return malloc(size + extra);
		}

		//(4) 這又是一個 placement new 
		void* operator new(size_t size, long extra, char init){
			cout << "operator new(size_t size, long extra, char init)  " << size << ' ' << extra << ' ' << init << endl;
			return malloc(size + extra);
		}

		//(5) 這又是一個 placement new, 但故意寫錯第一參數的 type (它必須是 size_t 以滿足正常的 operator new) 
		//!  	void* operator new(long extra, char init) { //[Error] 'operator new' takes type 'size_t' ('unsigned int') as first parameter [-fpermissive]
		//!	  	cout << "op-new(long,char)" << endl;
		//!    	return malloc(extra);
		//!  	} 	

		//以下是搭配上述 placement new 的各個 called placement delete. 
		//當 ctor 發出異常,這兒對應的 operator (placement) delete 就會被喚起. 
		//應該是要負責釋放其搭檔兄弟 (placement new) 分配所得的 memory.  
		//(1) 這個就是一般的 operator delete() 的重載 
		void operator delete(void*, size_t)
		{
			cout << "operator delete(void*,size_t)  " << endl;
		}

		//(2) 這是對應上述的 (2)  
		void operator delete(void*, void*)
		{
			cout << "operator delete(void*,void*)  " << endl;
		}

		//(3) 這是對應上述的 (3)  
		void operator delete(void*, long)
		{
			cout << "operator delete(void*,long)  " << endl;
		}

		//(4) 這是對應上述的 (4)  
		//如果沒有一一對應, 也不會有任何編譯報錯 
		void operator delete(void*, long, char)
		{
			cout << "operator delete(void*,long,char)  " << endl;
		}

	private:
		int m_i;
	};


	//-------------	
	void test_overload_placement_new()
	{
		cout << "\n\n\ntest_overload_placement_new().......... \n";

		Foo start;  //Foo::Foo

		Foo* p1 = new Foo;           //op-new(size_t)
		Foo* p2 = new (&start) Foo;  //op-new(size_t,void*)
		Foo* p3 = new (100) Foo;     //op-new(size_t,long)
		Foo* p4 = new (100, 'a') Foo; //op-new(size_t,long,char)

		Foo* p5 = new (100) Foo(1);     //op-new(size_t,long)  op-del(void*,long)
		Foo* p6 = new (100, 'a') Foo(1); //
		Foo* p7 = new (&start) Foo(1);  //
		Foo* p8 = new Foo(1);           //
		//VC6 warning C4291: 'void *__cdecl Foo::operator new(unsigned int)'
		//no matching operator delete found; memory will not be freed if
		//initialization throws an exception
	}
} //namespace	

int main(void)
{
	jj07::test_overload_placement_new();
	return 0;
}

编译运行结果如下:

五、pre-class allocator

案例如下:

#include <cstddef>
#include <iostream>
using namespace std;

namespace jj04
{
	//ref. C++Primer 3/e, p.765
	//per-class allocator 

	class Screen {
	public:
		Screen(int x) : i(x) { };
		int get() { return i; }

		void* operator new(size_t);
		void  operator delete(void*, size_t);	//(2)
		//! void  operator delete(void*);			//(1) 二擇一. 若(1)(2)並存,會有很奇怪的報錯 (摸不著頭緒) 

	private:
		Screen* next;
		static Screen* freeStore;
		static const int screenChunk;
	private:
		int i;
	};
	Screen* Screen::freeStore = 0;
	const int Screen::screenChunk = 24;

	void* Screen::operator new(size_t size)
	{
		Screen *p;
		if (!freeStore) {
			//linked list 是空的,所以攫取一大塊 memory
			//以下呼叫的是 global operator new
			size_t chunk = screenChunk * size;
			freeStore = p =
				reinterpret_cast<Screen*>(new char[chunk]);
			//將分配得來的一大塊 memory 當做 linked list 般小塊小塊串接起來
			for (; p != &freeStore[screenChunk - 1]; ++p)
				p->next = p + 1;
			p->next = 0;
		}
		p = freeStore;
		freeStore = freeStore->next;
		return p;
	}


	//! void Screen::operator delete(void *p)		//(1)
	void Screen::operator delete(void *p, size_t)	//(2)二擇一 
	{
		//將 deleted object 收回插入 free list 前端
		(static_cast<Screen*>(p))->next = freeStore;
		freeStore = static_cast<Screen*>(p);
	}

	//-------------
	void test_per_class_allocator_1()
	{
		cout << "\ntest_per_class_allocator_1().......... \n";

		cout << sizeof(Screen) << endl;		//8	

		size_t const N = 100;
		Screen* p[N];

		for (int i = 0; i< N; ++i)
			p[i] = new Screen(i);

		//輸出前 10 個 pointers, 用以比較其間隔 
		for (int i = 0; i< 10; ++i)
			cout << p[i] << endl;

		for (int i = 0; i< N; ++i)
			delete p[i];
	}
} //namespace

int main(void)
{
	jj04::test_per_class_allocator_1();
	return 0;
}

编译运行结果如下:

每个对象以8byte对齐。内存池本质上是分配了一大块内存,然后将该内存分割为多个小块通过链表拼接起来,所以物理上不一定连续但是逻辑上是连续的。

案例如下:

#include <cstddef>
#include <iostream>
using namespace std;

namespace jj05
{
	//ref. Effective C++ 2e, item10
	//per-class allocator 

	class Airplane {   //支援 customized memory management
	private:
		struct AirplaneRep {
			unsigned long miles;
			char type;
		};
	private:
		union {
			AirplaneRep rep;  //此針對 used object
			Airplane* next;   //此針對 free list
		};
	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 已空。配置一塊夠大記憶體,
			//令足夠容納 BLOCK_SIZE 個 Airplanes
			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; //以null結束

			// 將 p 設至頭部,將 headOfFreeList 設至
			// 下一個可被運用的小區塊。
			p = newBlock;
			headOfFreeList = &newBlock[1];
		}
		return p;
	}

	// operator delete 接獲一塊記憶體。
	// 如果它的大小正確,就把它加到 free list 的前端
	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;
	}

	//-------------
	void test_per_class_allocator_2()
	{
		cout << "\ntest_per_class_allocator_2().......... \n";

		cout << sizeof(Airplane) << endl;    //8

		size_t const N = 100;
		Airplane* p[N];

		for (int i = 0; i< N; ++i)
			p[i] = new Airplane;


		//隨機測試 object 正常否 
		p[1]->set(1000, 'A');
		p[5]->set(2000, 'B');
		p[9]->set(500000, 'C');
		cout << p[1] << ' ' << p[1]->getType() << ' ' << p[1]->getMiles() << endl;
		cout << p[5] << ' ' << p[5]->getType() << ' ' << p[5]->getMiles() << endl;
		cout << p[9] << ' ' << p[9]->getType() << ' ' << p[9]->getMiles() << endl;

		//輸出前 10 個 pointers, 用以比較其間隔 
		for (int i = 0; i< 10; ++i)
			cout << p[i] << endl;

		for (int i = 0; i< N; ++i)
			delete p[i];
	}
} //namespace

int main(void)
{
	jj05::test_per_class_allocator_2();
	return 0;
}

编译运行结果如下:

这种做法有几点比较有意思,首先是使用了union保存链表元素的next指针,这样整体上可以节省空间;其次是delete函数,它并没有直接将目标元素删除,而是将它当作下一个可分配的内存空间,也就是说如果delete某元素,那么该元素占有的内存空间不会被free掉,而是在下一次调用new时分配给新的对象。

六、static allocator

代码如下:

#include <cstddef>
#include <iostream>
#include <complex>
using namespace std;

namespace jj09
{

	class allocator
	{
	private:
		struct obj {
			struct obj* next;  //embedded pointer
		};
	public:
		void* allocate(size_t);
		void  deallocate(void*, size_t);
		void  check();

	private:
		obj* freeStore = nullptr;
		const int CHUNK = 5; //小一點方便觀察 
	};

	void* allocator::allocate(size_t size)
	{
		obj* p;

		if (!freeStore) {
			//linked list 是空的,所以攫取一大塊 memory
			size_t chunk = CHUNK * size;
			freeStore = p = (obj*)malloc(chunk);

			//cout << "empty. malloc: " << chunk << "  " << p << endl;

			//將分配得來的一大塊當做 linked list 般小塊小塊串接起來
			for (int i = 0; i < (CHUNK - 1); ++i)	{  //沒寫很漂亮, 不是重點無所謂.  
				p->next = (obj*)((char*)p + size);
				p = p->next;
			}
			p->next = nullptr;  //last       
		}
		p = freeStore;
		freeStore = freeStore->next;

		//cout << "p= " << p << "  freeStore= " << freeStore << endl;

		return p;
	}
	void allocator::deallocate(void* p, size_t)
	{
		//將 deleted object 收回插入 free list 前端
		((obj*)p)->next = freeStore;
		freeStore = (obj*)p;
	}
	void allocator::check()
	{
		obj* p = freeStore;
		int count = 0;

		while (p) {
			cout << p << endl;
			p = p->next;
			count++;
		}
		cout << count << endl;
	}
	//--------------

	class Foo {
	public:
		long L;
		string str;
		static allocator myAlloc;
	public:
		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;


	class Goo {
	public:
		complex<double> c;
		string str;
		static allocator myAlloc;
	public:
		Goo(const complex<double>& x) : c(x) {  }
		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 Goo::myAlloc;

	//-------------	
	void test_static_allocator_3()
	{
		cout << "\n\n\ntest_static_allocator().......... \n";

		{
			Foo* p[100];

			cout << "sizeof(Foo)= " << sizeof(Foo) << endl;
			for (int i = 0; i<23; ++i) {	//23,任意數, 隨意看看結果 
				p[i] = new Foo(i);
				cout << p[i] << ' ' << p[i]->L << endl;
			}
			//Foo::myAlloc.check();

			for (int i = 0; i<23; ++i) {
				delete p[i];
			}
			//Foo::myAlloc.check();
		}

		{
			Goo* p[100];

			cout << "sizeof(Goo)= " << sizeof(Goo) << endl;
			for (int i = 0; i<17; ++i) {	//17,任意數, 隨意看看結果 
				p[i] = new Goo(complex<double>(i, i));
				cout << p[i] << ' ' << p[i]->c << endl;
			}
			//Goo::myAlloc.check();

			for (int i = 0; i<17; ++i) {
				delete p[i];
			}
			//Goo::myAlloc.check();	
		}
	}
} //namespace	

int main(void)
{
	jj09::test_static_allocator_3();
	return 0;
}

编译运行结果如下:

之前的几个版本都是在类的内部重载了operator new()和operator delete()函数,这些版本都将分配内存的工作放在这些函数中,但现在的这个版本将这些分配内存的操作放在了allocator类中,这就渐渐接近了标准库的方法。从上面的代码中可以看到,两个类Foo和Goo中operator new()和operator delete()函数等很多部分代码类似,于是可以使用宏来将这些高度相似的代码提取出来,简化类的内部结构,但最后达到的结果是一样的:

七、global allocator

上面我们自己定义的分配器使用了一条链表来管理内存的,但标准库却用了多条链表来管理,这在后续会详细介绍:

八、new handler

如果用户调用new申请一块内存,如果由于系统原因或者申请内存过大导致申请失败,这时将抛出异常,在一些老的编译器中可能会直接返回0,可以参考上图右边代码,当无法分配内存时,operator new()函数内部将调用_calnewh()函数,这个函数通过左边的typedef传入,看程序员是否能自己写一个handler处理函数来处理该问题。一般有两个选择,让更多的Memory可用或者直接abort()或exit()。下面是测试的一个结果:

该部分中自定义了处理函数noMoreMemory()并通过set_new_handler来注册该处理函数,在BCB4编译器中会调用到自定义的noMoreMemory()函数,但在右边的dev c++中却没有调用,这个还要看平台。

九、=default和=delete

更加详细的内容可以参考下面这篇文章:

https://blog.csdn.net/u012333003/article/details/25299939

浪漫宇宙旗下知识库分享站

访客数--
|
访问量--