C++中動態內存申請的結果


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 值;


免責聲明!

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



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