C++中程序存儲空間除棧空間和靜態區外,每個程序還擁有一個內存池,這部分內存被稱為或堆(heap)。程序可以用堆來存儲動態分配的對象,即那些在程序運行時創建的對象。動態對象的生存期由程序來控制 ,當動態對象不再使用時,程序必須顯式的銷毀它們。new操作符就是從自由存儲區上為對象動態分配內存空間的。這里的自由存儲區可以是堆,或者靜態區。
1、new和delete的使用
C++中通過一對運算符new和delete來完成動態內存分配。new,在動態內存中為對象分配空間並返回一個指向該對象的指針,我們可以選擇對對象初始化;delete接受一個動態對象的指針,銷毀對象,並釋放對應內存。使用示例如下:
1 void Test() 2 { 3 int *pi1 = new int; 4 //pi1指向一個動態分配的4個字節(int型)、未初始化的無名對象;*pi1的值未定義
5 int *pi2 = new int(2); 6 //pi2指向的對象的值是2,動態分配4個字節( 1個 int) 的空間並初始化為2
7 int *pi3 = new int[3]; //動態分配12個字節( 3個 int) 的空間
8 int *pi4 = new int(); //值初始化為0;*pi4為0
9
10 delete pi1; 11 delete pi2; 12 delete [] pi3; 13 delete pi4; 14 }
自由空間分配的內存是無名的,new無法為其分配的對象命名,而是返回一個指向該對象的指針。默認情況下,動態分配的對象是默認初始化的,即內置類型或組合類型的對象的值是未定義的,類類型的對象將用默認構造函數進行初始化。
new和delete、 new[] 和delete[] 一定匹配使用 , 一定匹配使用 , 一定匹配使用 ! ! ! 重要的事說三遍! 否則可能出現內存泄露甚至崩潰的問題。
2、深入探究new和delete、 new[] 和delete[]內部實現機制
通過下面這段代碼我們來詳細比較一下new和delete、 new[] 和delete[]內部實現
1 class Array 2 { 3 public : 4 Array(size_t size = 10)//構造函數 5 : _size(size) 6 , _a(0) 7 { 8 cout << "Array(size_t size) " << endl; 9 if (_size > 0) 10 { 11 _a = new int[size]; 12 } 13 } 14 ~Array() //析構函數 15 { 16 cout << "~Array() " << endl; 17 if (_a) 18 { 19 delete[] _a; 20 _a = 0; 21 _size = 0; 22 } 23 } 24 private: 25 int*_a; 26 size_t _size; 27 }; 28 void Test() 29 { 30 Array* p1 = (Array*)malloc(sizeof(Array)); 31 Array* p2 = new Array; 32 Array* p3 = new Array(20); 33 Array* p4 = new Array[10]; 34 free(p1); 35 delete p2; 36 delete p3; 37 delete[] p4; 38 } 39 int main() 40 { 41 Test(); 42 getchar(); 43 return 0; 44 }
轉到反匯編可以看到,在call指令處調用了operator new:
轉到定義處可以看到operator new 的具體原型:
其實在operator new的底層同樣調用了malloc分配空間,它先為對象分配所申請的內存空間,然后底層調用構造函數構造對象
再按F10程序來到了構造函數
執行完之后,輸出
此時new已經完成了申請空間的任務,且調用構造函數創建了對象。同樣,detele的定義如下
而delete是先調用析構函數清除對象。然后調用operator detele釋放空間。
按F10跳轉到了析構函數,析構之后:
然后空間才被釋放:
new []與delete []內部執行過程相同,只是底部調用的是operator new []和operator delete []
Array* p4 = new Array[10];
delete[] p4;
執行這兩條語句的時候實際上調用operator new[](10*sizeof(Array)+4)分配大小為10*sizeof(Array)+4空間,其中多的四個字節空間用於存放N(10)這個數字以便於delete中調用析構函數析構對象(調用析構函數的次數),空間申請好了之后調用構造函數創建對象。delete[] p4執行的時候首先取N(10)對象個數,然后調用析構函數析構對象,最后用operator delete[]函數釋放空間。
3、關鍵字之間的匹配使用問題
1 void Test () 2 { 3 // 以下代碼沒有匹配使用, 會發生什么? 有內 存泄露嗎? 會崩 潰嗎? 4 int* p4 = new int; 5 int* p5 = new int(3) ; 6 int* p6 = new int[3] ; 7 int* p7 = (int*) malloc(sizeof (int) ) ; 8 delete[] p4 ; 9 delete p5 ; 10 free(p5 ) ; 11 delete p6 ; 12 delete p7 ; 13 }
運行結果:沒有崩潰。但是當把int換成自定義類型之后則會出現問題。因為內置類型一般不會調用構造函數和析構函數,而自定義類型會,所以是析構對象的時候出現內存泄漏,導致程序崩潰。雖然弄清了問題,但還是建議任何類型都要匹配使用。
4、定位new表達式
new表達式,默認下把內存開辟到堆區。使用定位new表達式,可以在指定地址區域(棧區、堆區、靜態區)構造對象,這好比是把內存開辟到指定區域。
定位new表達式調用 void *operator new(size_t, void *); 分配內存。其常見形式有:
1 new(address) type; 2 new(address) type(initializers); 3 new(address) type[size]; 4 new(address) type[size]{braced initializer list};
address必須是個指針,指向已經分配好的內存。
示例代碼:
1 #include <iostream> 2 using namespace std; 3 char addr1[100]; //把內存分配到全局/靜態區 4 int main() 5 { 6 char addr2[100]; //把內存分配到棧區 7 char *addr3 = new char[100]; //把內存分配到堆區 8 cout << "addr1 = " << (void*)addr1 << endl; 9 cout << "addr2 = " << (void*)addr2 << endl; 10 cout << "addr3 = " << (void*)addr3 << endl; 11 int *p = nullptr; 12 //把對象構造到靜態區 13 p = new(addr1)int; 14 *p = 1; 15 cout << (void*)p << " " << *p << endl; 16 //把對象構造到棧區 17 p = new(addr2)int; 18 *p = 2; 19 cout << (void*)p << " " << *p << endl; 20 //把內存分配到堆區 21 p = new(addr3)int; 22 *p = 3; 23 cout << (void*)p << " " << *p << endl; 24 cin.get(); 25 return 0; 26 }
程序中,首先使用變量或new為對象分配空間,然后通過定位new表達式,完成構造函數的調用,將對象創建在已經被分配好的內存中。
定位new表達式不能調用delete刪除 placement new的對象,需要人為的調用對象的析構函數,並且人為的釋放掉占用的內存。
1 #include <iostream> 2 #include <new> 3 4 using namespace std; 5 6 const int chunk = 16; 7 8 class Foo 9 { 10 public: 11 int val(){return _val;} 12 Foo(){_val=0;} 13 private: 14 int _val; 15 }; 16 17 int main() 18 { 19 // 預分配內存buf 20 char *buf = new char[sizeof(Foo) * chunk]; 21 22 // 在buf中創建一個Foo對象 23 Foo *pb=new (buf) Foo; 24 // 檢查一個對象是否被放在buf中 25 if(pb->val()==0) cout<<"new expression worked!"<<endl; 26 // 這里不存在與定位new表達式匹配的delete表達式,即:delete pb, 其實只是為了 27 // 釋放內存的話,我們不需要這樣的表達式,因為定位new表達式並不分配內存。 28 // 如果在析構函數中要做一些其他的操作呢?就要顯示的調用析構函數。 29 // 當程序不再需要buf時,buf指向的內存被刪除,它所包含的任何對象的生命期也就 30 // 都結束了。 31 32 delete[] buf; 33 return 0; 34 }
一句話:定位new表達式用於在已分配的原始空間中調用構造函數初始化一個對象。
5、模擬實現new和delete
1 class Test 2 {}; 3 4 Test* newdelete( ) 5 { 6 Test* p1 = NULL; 7 //1、分配空間 2.利用new的定位表達式顯式調用構造函數 8 if (p1 = (Test*)malloc(sizeof(Test))) 9 return p1; 10 else 11 throw bad_alloc(); //內存分配失敗時拋異常 12 new(p1)Test; //NEW(P1+I)Test(參數列表); 13 14 //3、析構函數 4、釋放空間 15 p1->~Test(); 16 free(p1); 17 } 18 19 Test* newdalete_(size_t N) 20 { 21 Test* p2 = NULL; 22 //1、分配空間 2.顯示調用構造函數 23 if (p2 = (Test*)malloc(sizeof(Test)*N + 4)) 24 return p2; 25 else 26 throw bad_alloc(); //內存分配失敗時拋異常 27 *((int*)p2) = N; 28 p2 = (Test*)((int*)p2 + 1); 29 for (int i = 0; i < N; ++i) 30 { 31 new(p2 + i)Test; 32 } 33 34 int n = *((int*)p2 - 1); 35 //3、析構函數 4、釋放空間 36 for (int i = 0; i < n; ++i) 37 { 38 p2[i].~Test(); 39 //(p1 + 1)->~AA(); //也可以 40 } 41 free((int*)p2 - 1); 42 }
總結:
1. operator new/operator delete operator new[] /operator delete[] 和 malloc/free用法一 樣。
2. 他們只負責分配空間/釋放空間, 不會調用對象構造函數/析構函數來初始化/清理對象。
3. 實際operator new和operator delete只是malloc和free的一層封裝。
【 new作用】 調用operator new分配空間。 調用構造函數初始化對象。
【 delete作用】 調用析構函數清理對象 調用operator delete釋放空間
【 new[] 作用】 調用operator new分配空間。 調用N次構造函數分別初始化每個對象。
【 delete[] 作用】 調用N次析構函數清理對象。 調用operator delete釋放空間。