c++中給對象分配記憶體常見有三種方法: 使用c++ 庫函數 std::allocator (c++ library); 使用new,new[] 表達式,::operator new() 操作符,(c++ primitives); c 函數 malloc/free (CRT); 測試代碼如下: 1 # ...
c++中給對象分配記憶體常見有三種方法:
- 使用c++ 庫函數 std::allocator (c++ library);
- 使用new,new[] 表達式,::operator new() 操作符,(c++ primitives);
- c 函數 malloc/free (CRT);
測試代碼如下:
1 #include<iostream> 2 #include <stdlib.h> 3 #include<complex> 4 #include <memory> //內含 std::allocator 5 #include <ext\pool_allocator.h> // __pool_alloc 6 7 using namespace std; 8 9 void test() 10 { 11 void* p1 = malloc(512); //512bytes 12 free(p1); 13 14 complex<int>* p2 = new complex<int>; //one object 15 delete p2; 16 17 void* p3 = ::operator new(512);// 512bytes 18 ::operator delete(p3); 19 20 #ifdef __GNUC__ 21 //以下函數都是non-static,一定要通過object調用,分配7個int的記憶體 22 void* p4 = allocator<int>().allocate(7);//allocator<int>()創建臨時對象 23 allocator<int>().deallocate((int*)p4,7); 24 25 //void* p5 = alloc::allocate(512); //2.9 26 //alloc::deallocate(p5,512); 27 28 void* p6 = __gnu_cxx::__pool_alloc<int>().allocate(9); // 對應上面的 gnc2.9; 29 __gnu_cxx::__pool_alloc<int>().deallocate((int*)p6,9); 30 #endif // __GNUC__ 31 32 #ifdef _MSC_VER_ 33 int* p6 = allocator<int>().allocate(3, (int*)0); 34 allocator<int>().deallocate(p6,3); 35 #endif // _MSC_VER_ 36 return; 37 }View Code
一、new
使用new表達式,編譯器將其轉化為先調用 operator new 運算符,然後調用構造函數。 new過程是先分配記憶體,然後調用構造函數;delete時,先調用析構函數,然後釋放記憶體。
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 <<endl; } }; void testCt() { string* pstr = new string; cout << "str = " << *pstr <<endl; // pstr->string::string("123"); //'class std::basic_string<char>' has no member named 'string'| pstr->~string(); cout<<"str = " <<endl; A* pA = new A(1); cout<< "pA->id = "<< pA->id<<endl; // pA->A::A(3);//error: cannot call constructor 'A::A' directly| // A::A(5); //error: cannot call constructor 'A::A' directly [-fpermissive]| cout<< "pA->id = "<<pA->id<<endl; delete pA; A* pA2; new(pA2)A(5); cout<< "pA2->id = "<<pA2->id<<endl; delete pA2; }View Code
二、Array new, Replacement new
定義數組A* p = new A[3];時,會申請分配記憶體,並調用三次預設構造函數; 當使用 delete[] p;時,會調用三次析構函數,並釋放記憶體,而使用delete p,則數組所占記憶體仍然會釋放掉,但只會調用一次析構函數,而如果析構函數內有釋放記憶體的操作,則使用delete p,造成記憶體泄漏。 測試代碼如下:1 void testArrNew() 2 { 3 A* buf = new A[3]; //預設構造函數調用3次 調用順序 0-1-2 4 //A必須有預設構造函數 new A[3]調用的是預設構造函數 5 6 A* tmp = buf;//記錄A數組的起點位置 7 8 cout << "buf=" << buf << " tmp=" << tmp << endl; 9 10 for(int i = 0; i < 3; i++) 11 { 12 new(tmp++)A(i); //placement new;在分配好的記憶體上,賦值 13 } 14 15 cout << "buf=" << buf << " tmp=" << tmp << endl; 16 17 //delete[] buf; 18 delete buf; 19 }View Code
執行結果如下:
三、 placement new
先看一下placement new
char* buf = new char[sizeof(A) * 3];//申請記憶體 A* pc = new(buf)A();//在申請好的buf的記憶體,在buf上賦值
代碼中 new(buf)A(); 就是placement new.
編譯器會將上述代碼轉化為
A * pc; try { void* men = operator new(sizeof(A), buf); //申請記憶體 pc = static_cast<A*>(mem);//轉換 pc->A::A();//構造函數 } catch (std::bad_alloc){ }
四、重載 operator new, operator new[], operator delete, operator delete[]
接管全局new,delete 函數,重載使用自己的操作符。
測試代碼如下:
新建class Foo類
1 class Foo 2 { 3 private: 4 int _id; 5 long _data; 6 string _str; 7 8 public: 9 Foo():_id(0) 10 { 11 cout << "default ctor.this=" << this << " id=" << _id << endl; 12 } 13 Foo(int a):_id(a) 14 { 15 cout << "ctor.this=" << this << " id=" << _id << endl; 16 } 17 18 virtual 19 ~Foo() 20 { 21 cout << "dtor.this=" << this << " id=" << _id << endl; 22 } 23 24 //申請記憶體的函數必須是靜態的 調用這個函數時一般都是正在創建這個對象 25 //所以當調用時,這個對象還不存在,需要聲明成靜態 26 static void* operator new(size_t size); 27 static void operator delete(void* pdead, size_t size); 28 static void* operator new[](size_t size); 29 static void operator delete[](void* pdead, size_t size); 30 }; 31 32 void* Foo::operator new(size_t size) 33 { 34 Foo* p = (Foo*)malloc(size); 35 cout <<"operator new().size="<< size << " return=" << p <<endl; 36 return p; //p 為記憶體起始點 37 } 38 39 void Foo::operator delete(void* pdead, size_t size)//pdead 刪除點,和上面的p為同一個位置,size 為將要刪除的記憶體大小 40 { 41 cout <<"operator delete.pdead=" << pdead << " size=" << size <<endl; 42 cout << endl; 43 free(pdead); 44 } 45 46 void* Foo::operator new[](size_t size) 47 { 48 Foo* p = (Foo*)malloc(size); 49 cout <<"operator new[].size="<< size <<" return=" << p << endl; 50 return p; 51 } 52 53 void Foo::operator delete[](void* pdead, size_t size) 54 { 55 cout<< "operator delete[].pdead=" << pdead << " size="<< size <<endl; 56 cout << endl; 57 free(pdead); 58 }class Foo
測試函數
1 void testoperatornew() 2 { 3 cout << "sizeof(Foo)="<<sizeof(Foo) << endl; 4 Foo* p = new Foo(7); 5 delete p; 6 7 Foo* pArray = new Foo[5]; 8 delete [] pArray; 9 }View Code
執行結果
重載new(),delete()
可以重載class member operator new(), 其中第一個參數必須是size_t,其餘參數以new所指定的placement arguments 為初值,出現於new() 括弧內的就是所謂的placems arguments.
也可以重載class member operator delete(),但他們不會被delete調用,只有當new所調用的ctor拋出異常,才會調用重載版的operator delete(),主要用來釋放未完全創建成功的object所占有的記憶體。
示例代碼
1 class Foo2 2 { 3 private: 4 int _id; 5 6 public: 7 Foo2() 8 { 9 cout << " Foo2()::Foo2() " << endl; 10 } 11 Foo2(int a) 12 { 13 cout << "Foo2()::Foo2(int) "<< endl; 14 throw Bad(); 15 } 16 17 18 void* operator new(size_t size) 19 { 20 cout << "operator new(size_t size), size="<< size <<endl; 21 return malloc(size); 22 } 23 24 // 標準庫提供的placeent new()的重載形式 25 void* operator new(size_t size, void* star) 26 { 27 cout << "operator new(size_t size), size="<< size<< " star = " << star <<endl; 28 return malloc(size); 29 } 30 // 模擬標準庫的形式,只傳回pointer 31 void* operator new(size_t size, long extra) 32 { 33 cout << "operator new(size_t size, long extra), size="<< size <<" extra = " << extra<<endl; 34 return malloc(size+extra); 35 } 36 37 void* operator new(size_t size, long extra,char init) 38 { 39 cout << "operator new(size_t size, long extra,char init), size="<< size <<" extra = " << extra 40 <<" init = " << init<<endl; 41 return malloc(size+extra); 42 } 43 /* 又一個 ,但故意寫錯第一參數類型 44 void* operator new(long extra, char init) //error: 'operator new' takes type 'size_t' ('unsigned int') 45 { 46 return malloc(extra); //as first parameter [-fpermissive]| 47 } */ 48 49 void operator delete(void*,long) 50 { 51 cout<<" operator delete(void*,size_t) " <<endl; 52 } 53 void operator delete(void*,long,char) 54 { 55 cout<<" operator delete(void*,long,char) " <<endl; 56 } 57 }; 58 59 void testFoo2() 60 { 61 Foo2 start; 62 Foo2* p1= new Foo2; 63 Foo2* p2= new(&start) Foo2; 64 Foo2* p3= new(100) Foo2; 65 Foo2* p4= new(100,'A') Foo2; 66 Foo2* p5= new(100) Foo2(1); 67 Foo2* p6= new(100,'A') Foo2(1); 68 Foo2* p7= new(&start) Foo2(1); 69 Foo2* p8= new Foo2(1); 70 }View Code
運行結果
五、記憶體池
記憶體池的優點
1.減少malloc的使用,提高運行效率
2.減少記憶體碎片,減少cookie
構造簡單的記憶體池,示例代碼如下
1 #include<iostream> 2 #include <stdlib.h> 3 #include<complex> 4 #include <memory> //內含 std::allocator 5 #include <ext\pool_allocator.h> // __pool_alloc 6 7 using namespace std; 8 9 class Screen 10 { 11 public: 12 Screen(int x): i(x) { } 13 int get() { return i; } 14 /************************重載 ××××*************/ 15 void* operator new(size_t); 16 void operator delete(void*, size_t); 17 /* **********************重載 ××××************ ***/ 18 private: 19 Screen* next;//4bit 20 static Screen* freeStore; 21 static const int screenChunk;//想要創建多少組 22 23 private: 24 int i; //4bit 25 }; 26 27 Screen* Screen::freeStore = 0; 28 const int Screen::screenChunk = 24; 29 30 /************************重載×××××××××××××××××××××××××*************/ 31 32 void* Screen::operator new(size_t size) 33 { 34 Screen* p; 35 if(!freeStore) 36 { 37 //linked list是空的,所以申請一大塊記憶體 38 size_t chunk = screenChunk * size; //192 Screen的記憶體大小為8共24組 24 * 8 = 192 39 freeStore = p = 40 reinterpret_cast<Screen*>(new char[chunk]); 41 cout << "startPisotion: " << p << endl; 42 43 //將一大塊記憶體分割成片段,當做linked list串接起來 44 for(; p != &freeStore[screenChunk-1]; ++p) 45 { 46 p->next = p+1; 47 } 48 p->next = 0; 49 } 50 p = freeStore; 51 freeStore = freeStore->next; 52 53 return p; 54 } 55 56 void Screen::operator delete(void* p, size_t) 57 { 58 //將delete object插回 free list前端 59 (static_cast<Screen*>(p)) -> next = freeStore; 60 freeStore = static_cast<Screen*>(p); 61 } 62 /************************重載×××××××××××××××××××××××××**************/ 63 void test_per() 64 { 65 cout << "sizeof(int)"<< sizeof(int*) << endl; 66 cout << "sizeof(Screen*)"<< sizeof(Screen*) << endl; 67 cout << "sizeof(Screen)"<< sizeof(Screen) << endl; 68 69 size_t const N = 10; 70 71 Screen* p[N]; 72 73 cout << "overload operator new" << endl; 74 for(int i=0; i<N; i++) 75 { 76 p[i] = new Screen(i); 77 } 78 79 for(int i = 0; i<10; i++) 80 { 81 cout << p[i] << endl;//輸出每個Screen的記憶體起點 82 } 83 84 for(int i=0; i<N; i++) 85 { 86 delete p[i]; 87 } 88 89 cout << "glob operator new" << endl; 90 91 Screen* q[N]; 92 93 for(int i=0; i<N; i++) 94 { 95 q[i] = ::new Screen(i); 96 } 97 98 for(int i = 0; i<10; i++) 99 { 100 cout << q[i] << endl; 101 } 102 103 for(int i=0; i<N; i++) 104 { 105 ::delete q[i]; 106 } 107 } 108 109 int main() 110 { 111 test_per(); 112 return 0; 113 }View Code
測試結果
左邊是重載了member operator new/delete 的結果,右邊是沒有使用重載而是使用global operator new/delete 的結果。class Screen 大小為8位元組,使用重載的函數數組內相鄰元素地址相隔8個位元組,減少了使用malloc時的cookie;而不使用重載函數,相隔48個位元組。
六、static allocate
上節分配記憶體的方法,每個類中都要重載new,delete; 因此將該部分提取出來,封裝為一個類allocate; 將應用類的實現與記憶體分配細節分離開來
測試代碼
1 #include<iostream> 2 #include <stdlib.h> 3 #include<complex> 4 //#include <memory> //內含 std::allocator 5 //#include <ext\pool_allocator.h> // __pool_alloc 6 7 using namespace std ; 8 9 class myAllocator 10 { 11 private: 12 struct obj 13 { 14 struct obj* next; 15 }; 16 17 public: 18 void* allocate(size_t); 19 void deallocate(void*, size_t); 20 private: 21 obj* freeStore = nullptr; 22 const int CHUNK = 5; // 便於觀察,設為5 23 }; 24 25 void myAllocator::deallocate(void* p, size_t size) 26 { 27 cout << "myAllocator::deallocate" << "size: " << size <<endl; 28 ((obj*)p)->next = freeStore; 29 freeStore = (obj*)p; 30 } 31 32 void* myAllocator::allocate(size_t size) 33 { 34 // cout << "myAllocator::allocate" << "size: " << size <<endl; 35 obj* p; 36 if(!freeStore) 37 { 38 size_t chunk = CHUNK * size;