C++學習筆記--new失敗後的處理

衆所周知,C++中使用new關鍵字申請內存成功時會返回申請的內存起始地址,並在該地址上調用構造函數。那麼,有考慮過失敗的情況嗎?

動態申請的處理方式

  • C語言
int *p = (int*)malloc(10000000000000000000);
if(NULL == p)
{
	//申請失敗處理分支
}
else
{
	//申請成功處理分支
	//.....
	free(p);
}
  • C++
int* p = new[10000000000000000000];
if(NULL == p)
{
	//申請失敗處理分支
}
else
{
	//申請成功分支
	//.....
	delete [] p;
}

動態申請的結果

C語言

  • 成功:返回申請成功的內存起始地址
  • 失敗:返回NULL

C++

  • 成功:返回申請成功的內存起始地址
  • 失敗:返回NULL或拋出std::bad_alloc異常,結果視編譯器的不同而不同。
  • 這是因爲在早期的C++編譯器中爲了兼容C編譯器的方式而做出的行爲,而在近代,C++由於支持拋出異常來處理一些可預見的異常情況後,C++編譯器就將拋出std::bad-alloc異常作爲new失敗的行爲,so,當申請失敗拋出異常時程序就不會執行到申請失敗的處理分支中。

new中的異常是怎麼拋出 異常的?

  • new關鍵字在空間不足導致內存分配失敗時
    • 會調用全局的new_handler類型的函數(包含在頭文件中)來處理調用失敗的情形,這樣就可能在new_handler函數中有機會騰出足夠的空間使得可以申請足夠的空間,但C++編譯器不知道具體怎麼操作,所以就有了個默認處理,即在new_handler函數裏拋出異常std::bad_alloc異常。
    • new_handler的類型是:void (*new_handler)(),它是一個函數指針。
    • 由於編譯器不知道如何整理內存 碎片,所以就可以自定義new_handler函數在不同環境中做不同的內存整理工作。

自定義new_handler函數及使用

  • new_handler函數定義,函數類型需要和new_handler類型一樣(即void(*)()類型),具體實現一般在工程中是整理內存碎片,也可以在此處拋出std::bad_alloc異常,由於是學習,所以使用粗暴的方式進行處理:
void my_new_handler()
{
	cout << "No enough memory" << endl;
	exit(1);
}
  • new_handler函數使用,使用new_handler set_new_handler(new_handler)函數設置在new失敗時需要調用的new_handler函數
  • set_new_handler的輸入參數是operator new分配內存失敗時要調用的出錯處理函數的指針(此處的my_new_handler),返回值是set_new_handler沒調用之前就已經在起作用的舊的出錯處理函數的指針(編譯器默認的全局new_handler函數)。
void ex_func()
{
	new_handler func = set_new_handler(my_new_handler);
	if(func)//爲何判斷,因爲有些編譯器
	{
		func();
	}
}

由於編譯器不同,如何進行行爲統一?

  • 全局範圍(風險太大,不推薦)
    • 重新定義new/delete的實現(操作符重載),不拋出任何異常
    • 自定義不拋出異常的new_handler函數,並設置給編譯器處理申請失敗的情形
  • 類層次範圍
    • 重載new/delete函數,不拋出異常
  • 單次動態內存分配
    • 在new時使用nothrow參數,指明不拋出異常,此時返回空指針

new使用實例

首先進行new/delete關鍵字的重載:

class Test
{
    int m_value;
public:
    Test()
    {
        cout << "Test()" << endl;
        m_value = 0;
    }
    
    ~Test()
    {
        cout << "~Test()" << endl;  
    }
    
    void* operator new (size_t size) //throw()
    {
        cout << "operator new: " << size << endl;
        return NULL;//模擬new失敗的情形
    }
    
    void operator delete (void* p)
    {
        cout << "operator delete: " << p << endl;
        free(p);
    }
    
    void* operator new[] (size_t size) throw()
    {
        cout << "operator new[]: " << size << endl;
        return NULL;//模擬new失敗的情形
    }
    
    void operator delete[] (void* p)
    {
        cout << "operator delete[]: " << p << endl;
        free(p);
    }
};

實例一:證明編譯器是否存在全局的new_handler處理函數

void my_new_handler()
{
	cout << "No enough memory" << endl;
	exit(1);
}
void ex_func_1()//證明編譯器是否帶有全局的new_handler處理函數
{
	new_handler func = set_new_handler(my_new_handler);
    try
    {
        cout << "func = " << func << endl;
        if( func )//爲何判斷,因爲存在一些編譯器不含全局的new_handler函數
        {
            func();
        }
    }
    catch(const bad_alloc&)//證明是否會拋出bad_alloc異常
    {
        cout << "catch(const bad_alloc&)" << endl;
    }
}
  • g++執行結果
    -
  • VS2010執行結果
    -
  • bcc編譯器執行結果
    -

根據上述結果可知,不同的編譯器確實存在差異,三款編譯器中只有bcc編譯器定義了全局的默認new_handler函數,並在new失敗時拋出異常。

統一編譯器的行爲:不拋出異常

  • 在重載的new/delete中添加throw()聲明告訴編譯器不拋出異常
    void* operator new (size_t size) throw()//聲明後就只返回空指針,不拋出異常
    {
        cout << "operator new: " << size << endl;
        return NULL;
    }
  • 使用new(nothrow) Test();//使用nothrow關鍵字告訴編譯器不拋出異常
    int* p = new(nothrow) int[10];//在指定空間申請內存並構造對象
    // ... ...
    delete[] p; 
  • new關鍵字在指定地址上分配內存
int bb[2] = {0};
    
    struct ST
    {
        int x;
        int y;
    };
    
    ST* pt = new(bb) ST();//指明在bb地址上創建對象
    
    pt->x = 1;
    pt->y = 2;
    
    cout << bb[0] << endl;//通過打印值證明是在bb上創建了對象
    cout << bb[1] << endl;
    
    pt->~ST();//在地址上指明調用構造函數後需要手動調用析構函數

在這裏插入圖片描述

當使用new申請一塊內存失敗時,拋出異常std::bad_alloc是C++標準中規定的標準行爲,所以推薦使用try{ p = new int[10000000]; } catch( std::bad_alloc ) { … }的處理方式。但是在一些老舊的編譯器中,卻不支持該標準,它會返回NULL,此時具有C傳統的判斷申請結果形式代碼便起了作用。所以,要針對不同的情形採取合理的處置方式,在工程中爲了增強可移植性可以採取相應方式(不拋出異常、new_handler函數處理)來統一new失敗的行爲。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章