1,問題:
1,動態內存申請一定成功嗎?
1,不一定成功;
2,常見的動態內存分配代碼:
1,C 代碼:
1 int* p = (int*)malloc(10 * sizeof(int)); 2 3 if( p != NULL ) 4 { 5 // ... ... 6 }
1,這種寫法合理;
2,C++ 代碼:
1 int* p = new int[10]; 2 3 if( p != NULL ) 4 { 5 // ... ... 6 }
1,古代編譯器這種寫法合理;
2,現代編譯器這種寫法就不合理,申請成功時,此語句沒有任何意義,申請失敗后,就會拋出一個標准庫中的異常對象,程序就不會向下執行到 if() 語句;
3,如果用的是一款現代的 C++ 編譯器,new 的結果無論成功或者失敗,根本用不着使用 if() 判斷語句;
3,申請內存失敗時:
1,malloc 函數申請失敗時返回 NULL 值;
2,new 關鍵字申請失敗時(根據編譯器的不同):
1,返回 NULL 值;
1,古代編譯器兼容 C 中方式,返回 NULL 值;
2,拋出 std::bad_alloc 異常;
1,近代編譯器不會返回 NULL 值,而是拋出一個標准庫中的 std::bad_alloc 異常;
4,問題:
1,new 語句中的異常是怎么拋出來的?
5,new 關鍵字在 C++ 規范中的標准行為:
1,在堆空間申請足夠大的內存;
1,成功:
1,在獲取的空間中調用構造函數創建對象;
2,返回對象的地址;
2,失敗:
1,C++ 規范定義要拋出 std::bad_alloc 異常;
2,new 在分配空間時:
1,如果空間不足,會調用全局的 new_handler() 函數(見 9 中例一);
1,調用 new_handler() 函數的意義在於我們可以有機會做一些處理,使得有更多的空間可以空出來;
2,整理空間、空出足夠的內存 C++ 編譯器是不知道的,這件事情具體的平台具體討論,於是 C++ 標准規范就給了默認的 new_handler() 實現,即拋出異常;
2,new_handler() 函數中拋出 std::bad_alloc 異常;
3,可以自定義 new_handler() 函數:
1,處理默認的 new 內存分配失敗的情況;
2,C++ 平台不能整理空間、空出足夠的內存,那么就交給我們自己來定義;
6,new_handler() 的定義和使用:
1,代碼示例:
1 void my_new_handler() 2 { 3 cout << "No enough memory"; 4 cout << endl; 5 6 exit(1); // 內存不足了,就把當前的程序結束了; 7 }
1,實際產品開發中,一般會嘗試在這個函數中進行內存的整理,整理后期望有更多的堆空間空出來,然后滿足當前程序對內存的需求;
2,也可以內存不足自己結束程序;
3,友好的方式是拋一個異常,然后進行異常處理,這是 C++ 默認的實現方式;
2,使用:
1 int main(int argc, char* argv[]) 2 { 3 set_new_handler(my_new_handler); // 告訴 C++ 編譯器;可以設置自定義的處理函數,處理堆空間不足的情況; 4 5 // ... ... 6 7 return 0; 8 }
7,問題:
1,如果跨編譯器統一 new 的行為,提高代碼移植性?
1,無論在任何情況下,申請失敗都返回空或者拋出異常;
2,為了兼顧古代編譯器,一般做法是自定義 new,使得 new 在申請堆空間失敗的時候,直接返回空指針,而不拋出異常;
8,解決方案:
1,全局范圍(不推薦):
1,重新定義 nwe/delete 的實現,不拋出任何異常;
2,自定義 new_handler() 函數,不拋出任何異常;
1,空函數擺在那里;
2,見本文 10.2.2.1 分析,這個方案對 VS 2010 編譯器是有用的;
3,不推薦,全局范圍重定義 new,風險是非常大的;
2,類層次范圍(推薦)(見本文 9 中例二):
1,重載 new/delete,不拋出任何異常;
2,失敗了返回空指針;
3,單次動態內存分配(見 本文9 中例三):
1,使用 nothrow 參數,指明 new 不拋出異常;
2,失敗了返回空指針;
9,動態內存申請編程實驗:
1 #include <iostream> 2 #include <new> 3 #include <cstdlib> 4 #include <exception> 5 6 using namespace std; 7 8 class Test 9 { 10 int m_value; 11 public: 12 Test() 13 { 14 cout << "Test()" << endl; 15 16 m_value = 0; 17 } 18 19 ~Test() 20 { 21 cout << "~Test()" << endl; 22 } 23 24 void* operator new (unsigned int size) throw() 25 { 26 cout << "operator new: " << size << endl; 27 28 // return malloc(size); 29 30 return NULL; // 這里當沒有加上 throw() 時,編譯器顯示: warning: 'operator new' must not return NULL unless it is declared 'throw()' (or -fcheck-new is in effect); 31 } 32 33 void operator delete (void* p) 34 { 35 cout << "operator delete: " << p << endl; 36 37 free(p); 38 } 39 40 void* operator new[] (unsigned int size) throw() 41 { 42 cout << "operator new[]: " << size << endl; 43 44 // return malloc(size); 45 46 return NULL; 47 } 48 49 void operator delete[] (void* p) 50 { 51 cout << "operator delete[]: " << p << endl; 52 53 free(p); 54 } 55 }; 56 57 void my_new_handler() 58 { 59 cout << "void my_new_handler()" << endl; 60 } 61 62 /* 證明 new_handler() 函數存在 */ 63 void ex_func_1() 64 { 65 /* 定義 func 變量,其類型為 new_handler 類型,C++ 中 new_handler 是一個預定義的函數指針,指向的函數類型是 void(*)(),調用 set_new_handler() 是將自定義的 my_new_handler() 處理函數設置進去,設置了自定義處理函數后,原來的處理函數就會作為返回值返回到 func,這點和上一節的不同,上一節是返回自定義的處理函數,這一節是返回原來的預定義處理函數; */ 66 new_handler func = set_new_handler(my_new_handler); 67 68 try 69 { 70 cout << "func = " << func << endl; 71 72 if( func ) // 加上 if() 處理語句是因為默認的情況下面可能是沒有處理函數的,此時 func 為空; 73 { 74 func(); 75 } 76 } 77 catch(const bad_alloc&) // 想證明默認的 new_handler() 處理函數確實是要拋出 bad_alloc 異常; 78 { 79 cout << "catch(const bad_alloc&)" << endl; 80 } 81 } 82 83 void ex_func_2() 84 { 85 Test* pt = new Test(); 86 87 cout << "pt = " << pt << endl; 88 89 delete pt; 90 91 pt = new Test[5]; 92 93 cout << "pt = " << pt << endl; 94 95 delete[] pt; 96 } 97 98 /* 如何在單次申請的時候,告訴編譯器,不管結果是什么,都不要拋出異常,如果說申請失敗了,直接返回空指針 */ 99 void ex_func_3() 100 { 101 /* 這個語句是 C++ 標准語法,只是之前沒見過而已 */ 102 int* p = new(nothrow) int[10]; // 現在進行動態內存申請,但是不管結果有沒有成功,都不要拋出異常,結果失敗,直接返回空; 103 104 // ... ... 105 106 delete[] p; 107 108 /* 上面 new 的寫法也可以寫成下面的形式 */ 109 110 int bb[2] = {0}; 111 112 struct ST 113 { 114 int x; 115 int y; 116 }; 117 118 ST* pt = new(bb) ST(); // 把 ST 對象創建到 bb[2] 的棧空間中去,即在指定的位置創建一個對象,括號的作用是向編譯器指明要在指定的地址上面創建一個對象出來; 119 120 /* 對創建的對象賦值 */ 121 pt->x = 1; 122 pt->y = 2; 123 124 /* 這里的打印想證明上面創建的對象確實存在 bb[2] 空間當中的 */ 125 cout << bb[0] << endl; // 打印 1 126 cout << bb[1] << endl; // 打印 2 127 128 pt->~ST(); // 顯示的調用析構函數,因為我們指定了穿件對象的空間,這時必須顯示手動調用析構函數; 129 } 130 131 int main(int argc, char *argv[]) 132 { 133 // ex_func_1(); 134 // ex_func_2(); 135 // ex_func_3(); 136 137 return 0; 138 }
1,ex_func_1() 打印結果:
1,g++ 編譯器:
func = 0;說明默認的情況下,g++ 編譯器並沒有一個全局的 new_handler() 處理函數;
2,VS 2010 編譯器:
func = 00000000;說明默認的情況下,VS 2010 編譯器並沒有一個全局的 new_handler() 處理函數;
3,BCC 編譯器:
func = 00401474
catch(const bad_alloc&);說明 BCC 的實現當中,確實是有一個全局 new_handler() 函數,它在調用后確實拋出了一個 bad_alloc 異常;
2,第一個打印結果說明不同的 C++ 編譯器 new 的行為是有一點不一樣的,具 體在 new 失敗時;
3,ex_func_2() 打印 new 的結果:
1,g++ 編譯器:
operator new: 4
Test()
段錯誤;因為 new 的重載函數返回空,於是 C++ 編譯器在空地址上面調用構造函數創建對象,並且在構造函數中做了一個賦值操作,這等價於對 0 地址處進行賦值,所以段錯誤;
2,VS 2010 編譯器:
operator new: 4
pt = 00000000;這款編譯器中,如果 new 的結果返回為空,是不會調用構造函數的;
3,BCC 編譯器:
operator new: 4
pt = 00000000;這款編譯器中,如果 new 的結果返回為空,是不會調用構造函數的;
4,不管哪款編譯器,申請動態內存失敗,直接返回空指針,不要干其它多余操作,則對 new 和 new[] 的重載進行異常規格說明,說明無論如何都不會扔出異常;
1,g++ 編譯器:
operator new: 4
pt = 0;和 VS 2010 以及 BCC 編譯器行為統一了;
5,ex_func_2() 打印 new[] 的結果:
1,g++ 編譯器:
operator new[]: 24
pt = 0
2,VS 2010 編譯器:
operator new[]: 24
pt = 00000000
3,BCC 編譯器:
operator new[]: 24
pt = 00000000
operator delete[]: 00000000
6,ex_func_3() 打印結果:
1,g++ 編譯器:
1
2
2,VS 2010 編譯器:
1
2
3,BCC 編譯器:
1
2
7,new 關鍵字可以在指定的空間上面創建對象,如果顯示的指定穿件對象的內存,就要顯示的手動調用析構函數;
8,三款編譯器的行為都一樣了;
10,實驗結論:
1,不是所有的編譯器都遵循 C++ 的標准規范;
2,編譯器可能重定義 new 的實現,並在實現中拋出 bad_alloc 異常;
1,雖然現代的編譯器在 new 失敗的時候會拋出一個 bad_alloc 異常,但是這個異常不一定就是在默認的 new_handler() 函數里面拋出來的,只有(上述三款編譯器) BCC 編譯器設置了默認的全局的 new_handler() 函數;
2,分析 VS 2010 new 如何實現,以觀察 new_handler() 如何實現:
3,編譯器的默認實現中,可能沒有設置全局的 new_handler() 函數;
4,對於移植性要求高的代碼,需要考慮 new 的具體細節;
11,小結:
1,不同的編譯器在動態內存分配上的實現細節不同;
2,malloc 函數在內存申請失敗時返回 NULL 值;
3,new 關鍵字在內存申請失敗時:
1,可能返回 NULL 值;
2,可能拋出 bad_alloc 值;