轉自:http://www.51testing.com/html/70/n-827070.html
在C++語言中,我們經常會使用new給一個對象分配內存空間,而當內存不夠會出現內存不足的情況。C++提供了兩種報告方式:
1、拋出bad_alloc異常來報告分配失敗;
2、返回空指針,而不會拋出異常。
C++爲什麼會採用這兩種方式呢?這主要是由於各大編譯器公司設計C++編譯器公司的結果,因爲標準C++是提供了異常機制的。例如,VC++6.0中當new分配內存失敗時會返回空指針,而不會拋出異常。而gcc的編譯器對於C++標準支持比較好,所以當new分配內存失敗時會拋出異常。
究竟爲什麼會出現這種情況呢?
首先,C++是在C語言的基礎之上發展而來,而且C++發明時是想儘可能的與C語言兼容。而C語言是一種沒有異常機制的語言,所以C++應該會提供一種沒有異常機制的new分配內存失敗報告機制;(確實是如此,早期的C++還沒有加入異常機制)
其次在返回空指針的實現過程中,C++採用的是malloc/calloc 等分配內存的函數,該類函數不會拋出異常,但是在分配內存失敗時會返回“空指針”。
最後,對於標準的C++,有着比較完善的異常處理機制,所以對於出現異常時,會拋出響應的異常。對於new分配失敗時,系統會拋出bad_alloc異常。
鑑於以上原因,我們在不同的編譯器需要new分配失敗時做不同的處理。例如:
情況1:
int* p = new int(5);
if ( p == 0 ) // 檢查 p 是否空指針
return -1;
情況2:
try {
int* p = new int(5);
// 其它代碼
} catch ( const bad_alloc& e ) {
return -1;
}
情況1和情況2的代碼都是對於new失敗時的處理,而針對不同的編譯器,這種處理會完全失效。如果在gcc編譯器採用情況1,那麼if(p==0)完全是沒有意義的,因爲不管new內存分配成功失敗與否,都不會出現p=0的情況。即,如果分配成功,p=0完全不可能;而分配失敗,new會拋出異常跳過其後面的代碼。而需要採用情況2的處理方式,即應該來捕捉異常。
同樣,如果在VC++6.0中採用情況2的代碼,那麼new分配失敗時,完全不會拋出異常,那麼捕捉異常也是徒勞的。
所以在new分配內存的異常處理時要特別小心,可以兩種方式聯合使用,來解決跨平臺跨編譯器的難題。
當然情況2中的異常處理代碼是最簡單的處理方式,下面我們爲其制定一個客戶制定的錯誤處理函數,即new-handler。
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
這裏首先定義new-handler函數指針類型,然後定義一個new-handler函數set_new_handler,其參數是指向operator new無法分配足夠內存時應該被調用的函數。其返回指也是一個指針,指向set_new_handler被調用前正在執行(但是馬上就要被替換)的那個new-handler函數。下面設計一個當operator new無法分配足夠內存時應該被調用的函數:
void noMemoryToAlloc()
{
std::cerr << "unable to satisfy request for memory\n";
std::abort();
}
使用noMemoryToAlloc函數的代碼爲:
int main()
{
set_new_handler(nomorememory);
int *pArray = new int[100000000000000L];
...
}
當operator new無法分配足夠空間時,noMemoryToAlloc就會被調用,於是程序就會發出一個錯誤信息cerr之後,調用abort函數結束程序。
如果operator new無法分配足夠空間時,我們希望不斷調用new-handler函數,直到找到足夠內存爲止,那麼我們的operator new函數就可以設計爲:
void *operator new(std::size_t size) throw(std::bad_alloc)
{
if ( size==0 ) {
size = 1;
}
while (true) {
調用malloc等內存分配函數來嘗試分配size大小的內存;
if ( 分配成功 )
return 指向分配得來的內存指針;
new_handler globalHandler = set_new_handler(0);
set_new_handle(globalHandler);
if(globalHandler)
(*globalHandler)();
else
throw std::bad_alloc();
}
}
而當new操作失敗時,一個好的程序不能簡單的終止程序就行了,而是要嘗試去釋放內存
如何能在new操作失敗,在拋出異常之前先把相應的處理做了呢?這就要用到new_handler了,它是在拋出exception調用的。爲了指定這個所謂的“內存不足處理函數,new_handler”,client必須調用set_new_handler,這是頭文件提供的函數,用法:
typedef void (*new_handler)()
new_handler set_new_handler(new_handler p) throw();
簡單的程序測試:
#include "stdafx.h"
#include
#include
using namespace std;
void NoMoreMemory()
{
cout<<"Unable to statisty request for memory"<<endl;
abort();
}
int _tmain(int argc, _TCHAR* argv[])
{
set_new_handler(NoMoreMemory);
int *p=new int[536870911];
return 0;
}
注:當operator new 無法滿足內存需求時,它會不只一次地調用new_handler函數(如果new_handler沒有退出程序的話);它會不斷地調用,直到找到足夠的內存爲止。因此一個良好設計的new_handler函數必須完成下列事情之一:
1)讓更多的內存可用。這或許能夠讓operator new 的下一次內存配置行爲成功。實現此策略的方法之一就是在程序起始時配置一大塊內存,然後在new_handler第一次被調用時釋放之。如此的釋放動作常常伴隨着某種警告信息,告訴用戶目前的內存已經處於低水位,再來的內存需求可能失敗,除非有更多的內存回覆自由身。
2)安裝一個不同的new_handler,如果目前的new_handler無法讓更多的內存可用,或許它知道另一個new_handler手上我有比較多的資源。果真如此,目前的new_handler就可以安裝另外一個new_handler以取代自己(只要再調用一次set_new_handler即可)。當operator new下次調用new_handler函數時,它會調用最新安裝的那個。
3)卸除掉這個new_handler,也就是說,將null指針傳給set_new_handler,一旦沒有安裝任何new_handler,operator new就會在內存配置失敗時拋出一個類型爲std::bad_alloc的exception。
4)拋出一個exception,類型爲std::bad_alloc(或者派生類型)。這樣的exception不會被operator new捕獲,所以它們就會傳動到最初踢出內存需求的那個點上
5)不返回,直接調用abort或exit
在MFC中
char *s_pNoMemoryPreAllocateBuff = new char[10240];
int no_memory (size_t nSize)
{
if(s_pNoMemoryPreAllocateBuff)
{
delete []s_pNoMemoryPreAllocateBuff;
s_pNoMemoryPreAllocateBuff = NULL;
}
CString strMsg;
strMsg.Format(L"Failed to allocate memory( %ld bytes )!", nSize);
AfxMessageBox(strMsg.GetString());
_asm INT 3; //調用斷點中斷
return 0;
}
應用時:
AfxSetNewHandler(no_memory);