new/new[]和delete/delete[]是如何分配空間以及釋放空間的


    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釋放空間。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM