new與delete


【1】malloc與free  和 new與delete

(1)malloc與free是C語言的標准庫函數。new與delete是C++的運算符。它們都可以申請與釋放動態內存。

(2)對於非內部數據類型的對象而言,用malloc與free無法滿足動態對象的要求(對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數)。

(3)由於malloc/free是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和析構函數的任務強加於malloc/free。

  因此,C++語言需要可以完成動態內存分配與初始化工作的運算符new,以及一個能完成清理與釋放內存工作的運算符delete。

(4)都是在堆(heap)上進行動態的內存操作。用malloc函數需要指定內存分配的字節數並且不能初始化對象。new會自動調用對象的構造函數。

  delete會調用對象的destructor,而free不會調用對象的destructor。

【2】描述內存分配方式以及它們的區別?

(1)從靜態存儲區域分配。靜態內存區域在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都一直存在。例如:全局變量,static 變量的存儲區域。

(2)在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集。

(3)從堆上分配(動態內存分配)。程序在運行的時候用malloc 或 new 申請任意多少的內存,程序員自己負責在何時用free 或 delete 釋放內存。

  動態內存的生存期由程序員決定,使用非常靈活,但問題也最多。

【3】malloc 與 free

(1)malloc函數分配的空間一定要用free函數釋放掉。

(2)free(p) 僅僅指釋放了malloc分配的空間,但是p指針仍然不為空,所以,在free函數釋放后一般要置空!防止野指針!

(3)非空指針只可以釋放一次。

(4)一般兩者搭配使用。

(5)malloc 與 free示例代碼如下:

 1 #include <iostream>
 2 #include <cstdlib>
 3 using namespace std;
 4 
 5 void main()
 6 {
 7     int *p = NULL ;  // 指針定義最好初始化為空(程序員基本素養)
 8 
 9     // 空指針釋放多次沒有任何意義
10     free(p);   // 一次!編譯通過!運行通過!
11     free(p);   // 二次!編譯通過!運行通過!
12 
13     p = (int *)malloc(sizeof(int) * 5);
14     if (NULL == p)
15     {
16         cout << "malloc failed!" << endl;
17         exit(1);
18     }
19     else
20     {
21         p[0] = 0;       // 注意賦值形式
22         p[4] = 4;
23 //      p[5] = 100;       // 編譯可以通過,但是運行錯誤error::因為p[5]越界
24 
25         cout << "p[4]: " << p[4] << endl;   // 4
26           
27         cout << "p[1]: " << p[1] << endl;   // 隨機數!!
28      }
29 
30     free(p);  // malloc申請空間使用free釋放(固定搭配)
31 
32     if (NULL == p)
33     {
34         cout << "free(p) 后 p == NULL" << endl;
35     }
36     else
37     {
38         cout << "free(p) 后 p != NULL" << endl;   // 野指針!!!
39 //      p[2] = 100;  // 編譯可以通過,運行時崩潰!!因為野指針所致。
40     }
41 
42 //  free(p);    // 編譯可以通過,運行時崩潰!因為已經釋放了一次,再次釋放導致錯誤。
43 
44     p = NULL;  // 徹底預防了它的隱患
45 
46     //........
47     system("pause");
48 }
49 
50 // run out:
51 /*
52 p[4]: 4
53 p[1]: -842150451
54 free(p) 后 p != NULL
55 請按任意鍵繼續. . .
56 */

【4】new 與 delete

(1)new的三種形態

到目前為止,C++相關資料書籍談及的new至少代表以下三種含義:

<1> new operator : new 運算符 (當然,書面稱法。個人覺得還是按照習慣稱作關鍵字new,以下此種形態均稱關鍵字 new

<2> operator new : 操作符  new(當然,書面稱法。個人覺得稱為new函數,以下此種形態均稱new函數

<3> placement new: 安置 new(C++primer上的稱法)

(2)關鍵字new

平常我們使用最多的就是關鍵字new。它由語言內建,不能重載,不能改變其行為。

關鍵字new在堆上動態創建一個對象時,它實際上做了三件事:

1:申請獲得一塊動態內存空間

2:調用對象的構造函數初始化對象內容

3:返回目的指針,即構建對象所申請的動態內存空間的指針

當然,如果我們創建的是內置類型的變量,那么第二步會被省略。

示例代碼如下:

 1 #include <iostream>
 2 #include <cassert>
 3 using namespace std;
 4 
 5 class A 
 6 { 
 7     int i;
 8 
 9 public: 
10     A (int _i = 2) : i(_i * _i) 
11     {
12         cout << "constructor   " << this << endl;
13     } 
14     void Print() 
15     { 
16         cout << i << endl; 
17     } 
18     ~A()
19     {
20         cout << "destructor  " << this << endl;
21     }
22 }; 
23 
24 void main()
25 {
26     /*
27      * 內置類型示例代碼
28      */
29     int *p1 = NULL;
30     p1 = new int(10);
31     assert(p1 != NULL);
32     cout << *p1 << endl;     // 10
33     delete p1;
34     p1 = NULL;
35 
36 
37     int *p2 = NULL;
38     p2 = new  int[5];   // 申請5份int類型大小的空間
39     assert(p2 != NULL);
40     delete []p2;       // 釋放數組變量
41     p2 = NULL;
42 
43     /*
44     * 自定義類型示代碼
45     */
46     A *p3 = NULL;
47     p3 = new A;       // 調用默認復合構造函數
48     assert(p3 != NULL);
49     p3->Print();      // 4
50     cout << "delete obj" << endl;
51     delete p3;
52     p3 = NULL;
53 
54     A *p4 = NULL;
55     p4 = new A[5];       // 調用默認復合構造函數 注意數組
56     assert(p4 != NULL);
57     p4[0].Print();    // 4
58     cout << "delete obj" << endl;
59     delete []p4;
60     p4 = NULL;
61 
62     A *p5 = NULL;
63     p5 = new A(10);       // 調用復合默認構造函數   注意區別
64     assert(p5 != NULL);
65     p5[0].Print();    // 100
66     cout << "delete obj" << endl;
67     delete p5;
68     p5 = NULL;
69 
70     system("pause");
71 }
72 
73 // run out:
74 /*
75 10
76 constructor   00514890
77 4
78 delete obj
79 destructor  00514890
80 constructor   00514924
81 constructor   00514928
82 constructor   0051492C
83 constructor   00514930
84 constructor   00514934
85 4
86 delete obj
87 destructor  00514934
88 destructor  00514930
89 destructor  0051492C
90 destructor  00514928
91 destructor  00514924
92 constructor   00514890
93 100
94 delete obj
95 destructor  00514890
96 請按任意鍵繼續. . .
97  */

(3)函數new

關鍵字new第一步分配內存實際上是通過調用new函數來完成的,而這里的new就是像加減乘除一樣的操作符,因此是可以重載的。

new函數默認情況下首先調用分配內存的代碼,嘗試得到一段堆上的空間,如果成功就返回;如果失敗,則轉而去調用一個new_hander,然后繼續重復前面過程。

如果我們對這個過程不滿意,就可以重載operator new,來設置我們希望的行為。

示例代碼如下:

  1 #include <iostream>
  2 #include <cassert>
  3 using namespace std;
  4 
  5 class A 
  6 { 
  7     int i;
  8 
  9 public: 
 10     A (int _i = 2) : i(_i * _i) 
 11     {
 12         cout << "constructor   " << this << endl;
 13     } 
 14     void Print() 
 15     { 
 16         cout << i << endl; 
 17     } 
 18     ~A()
 19     {
 20         cout << "destructor  " << this << endl;
 21     }
 22 }; 
 23 
 24 /*
 25  * 重載全局new/delete函數
 26  */
 27 void * operator new(size_t size)
 28 {
 29     cout << " overload operator new " << endl;
 30     void *p = malloc(size);
 31     return (p);
 32 }
 33 
 34 void operator delete(void *p)
 35 {
 36     cout << " overload operator delete " << endl;
 37     free(p);
 38 }
 39 
 40 void main()
 41 {
 42     /*
 43      * 內置類型示例代碼
 44      */
 45     int *p1 = NULL;
 46     p1 = (int *)::operator new(sizeof(int));
 47     new(p1) int(10);   // 第一種賦值方式  
 48     assert(p1 != NULL);
 49     cout << *p1 << endl;     // 10
 50     ::operator delete(p1);
 51     p1 = NULL;
 52 
 53 
 54     int *ptr = NULL;
 55     ptr = (int *)::operator new(sizeof(int));
 56     *ptr = 100;    // 第二種賦值方式  
 57     assert(ptr != NULL);
 58     cout << *ptr << endl;      // 100
 59     delete ptr;
 60     ptr = NULL;
 61 
 62     int *p2 = NULL;
 63     p2 = (int *)::operator new(sizeof(int) * 5);   // 申請5份int類型大小的空間
 64     assert(p2 != NULL);
 65     for (int i = 0; i < 5; ++i)
 66     {
 67         p2[i] =  i + 10;   // 數組變量的賦值
 68     }
 69     for (int i = 0; i < 5; ++i)
 70     {
 71         cout << p2[i] << endl;   // 10 11 12 13 14 
 72     }
 73     ::operator delete[] (p2);       // 釋放數組變量
 74     p2 = NULL;
 75 
 76     /*
 77     * 自定義類型示例代碼
 78     */
 79     A *p3 = NULL;
 80     p3 = (A *)::operator new(sizeof(A));     
 81     assert(p3 != NULL);
 82     new(p3) A(10);
 83     p3->Print();       // 100
 84     cout << "delete obj" << endl;
 85     p3->~A();    // 先調用對象析構函數
 86     ::operator delete(p3);  // 再釋放申請內存
 87     p3 = NULL;
 88 
 89     // 注意差別
 90     A *p4 = NULL;
 91     p4 = (A *)::operator new(sizeof(A));     
 92     assert(p4 != NULL);
 93     new(p4) A(10);
 94     p4->Print();       // 100
 95     cout << "delete obj" << endl;
 96     ::operator delete(p4);  // 直接釋放申請內存
 97     p4 = NULL;
 98 
 99     system("pause");
100 }
101 
102 // run out:
103 /*
104  overload operator new
105 10
106  overload operator delete
107  overload operator new
108 100
109  overload operator delete
110  overload operator new
111 10
112 11
113 12
114 13
115 14
116  overload operator new
117 constructor   00434890
118 100
119 delete obj
120 destructor  00434890
121  overload operator delete
122  overload operator new
123 constructor   00434890
124 100
125 delete obj
126  overload operator delete
127 請按任意鍵繼續. . .
128  */

以上這段代碼建議最好調試逐步看看,詳細分析一下運行結果,然后認真總結一下。

下面比較new關鍵字與new函數的區別:

<1> new關鍵字

int *ptr = new  int(100);

1:分配內存; 2:賦初始值; 3:類型自動匹配; 4:大小自動。

<2> new函數

int *ptr = (int *)::operator new(sizeof(int) * 5);

1:分配內存; 2:無初始化; 3:類型轉換; 4:大小手動。

(4)安置new

關鍵字new可以說為了應用的方便性考慮,其本質也是調用new函數進行內存配置的,然后再根據申請類型創建並初始化對象具體內容。

那么,假如試想一下這個情境:我們現在已申請到了一塊內存,但臨時突然想在這塊內存空間上構建另一個對象呢?好,安置new當仁不讓。

安置new是用來實現定位構造的,因此可以實現關鍵字new三步操作中的第二步:

也就是,在取得了一塊可以容納指定類型對象(變量)的內存后,在這塊內存上構造一個對象(變量)。

示例代碼如下:

關於對象的構建,上面new函數的示例代碼中已經很具體。在此,特別示例定位new也可以構造棧上的內存。

 1 #include <iostream>
 2 #include <cassert>
 3 //#include<new.h>   //有些資料書提醒必須加這個頭文件,VS2010下可以省略。
 4 using namespace std;
 5 
 6 class A 
 7 { 
 8     int i;
 9 
10 public: 
11     A (int _i = 2) : i(_i * _i) 
12     {
13         cout << "constructor   " << this << endl;
14     } 
15     void Print() 
16     { 
17         cout << i << endl; 
18     } 
19     ~A()
20     {
21         cout << "destructor  " << this << endl;
22     }
23 
24 }; 
25 
26 
27 void main()
28 {
29     char s[sizeof(A)]; 
30     A* p = (A*)s; 
31     new(p) A(3);   // 定位new的用法
32     p->Print(); 
33     p->~A();  // 不過必須要顯式調用析構函數
34     system("pause");
35 }
36 
37 // run out:
38 /*
39 constructor   001DFCA8
40 9
41 destructor  001DFCA8
42 請按任意鍵繼續. . .
43  */

這里“new(p) A(3)”這種“奇怪的”寫法即是安置new的用法,它實現了在指定內存空間用指定類型的構造函數來構造一個對象的功能,后面A(3)就是對構造函數的顯式調用。

通過上面的例子,我們可以看到這塊指定的地址既可以是棧內存,又可以是堆內存,安置new對此不加區分。

但是,除非特別必要,不要直接使用安置new ,這畢竟不是用來構造對象的正式寫法,只不過是new函數的一個步驟而已。

使用關鍵字new地編譯器會自動生成對安置new的調用的代碼,因此也會相應的生成使用delete時調用析構函數的代碼。

如果是像上面那樣在棧上使用了安置new,則必須手工調用析構函數,這也是顯式調用析構函數的唯一情況: p->~A();

當我們覺得默認的關鍵字new對內存的管理不能滿足我們的需要,而希望自己手工的管理內存時,安置new就有用了。

STL中的allocator就使用了這種方式,借助安置new來實現更靈活有效的內存管理。 

【5】new的基本使用指南

(1)如果想在堆上建立一個對象,應該用關鍵字new 。它既分配內存又為對象調用構造函數。

(2)如果僅僅想分配內存,就應該調用 new 函數;它不會調用構造函數。

如果想定制在堆對象被建立時的內存分配過程,你應該寫自己的new 函數,然后使用new關鍵字, new 關鍵字會調用定制的 operator new .

(3)如果想在一塊已經獲得指針的內存里建立一個對象,應該用 安置new 。安置new 主要適用於:

<1> 在對時間要求非常高的應用程序中,因為這些程序分配的時間是確定的;

<2> 長時間運行而不被打斷的程序;

<3> 以及執行一個垃圾收集器(garbage collector)。

 

Good Good Study, Day Day Up.

順序  選擇  循環  堅持  總結


免責聲明!

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



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