衆所周知,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();//在地址上指明調用構造函數後需要手動調用析構函數