動態記憶體的結果
問題: 動態記憶體申請一定成功嗎?
- 常見的動態記憶體分配程式碼
C 程式碼:
void code()
{
int* p = (int*)malloc(10 * sizeof(int));
if( p != NULL )
{
// ...
}
free(p);
}
C++ 程式碼:
void code()
{
int* p = new int[10];
if( p != NULL )
{
// ...
}
delete p;
}
必須知道的事實
- malloc 函式申請失敗時返回 NULL 值
new 關鍵字申請失敗時(根據編譯器不同)
- 返回 NULL 值 (古代)
- 丟擲 std::bad_alloc 異常 (現代)
問題: new 語句中的異常是怎麼丟擲來的呢?
new 關鍵字在 C++ 規範中的標準行為
在堆空間申請足夠的記憶體
成功:
- 在獲取的空間中呼叫建構函式建立物件
- 返回物件的地址
失敗
- 丟擲 std::bad_alloc 異常
new 關鍵字在 C++ 規範中的標準行為
new 在分配記憶體時
- 如果空間不足,會呼叫全域性的 new_handler() 函式
- new_handler() 函式中丟擲 std::bad_alloc 異常
可以自定義 new_handler() 函式
- 處理預設的 new 記憶體分配失敗的情況
new_handler() 中,可以手動做一些記憶體整理的工作,使得更多的堆空間可以被使用。
new_handler() 函式的替換
- 自定義一個無返回值無引數的函式
呼叫 set_new_handler() 設定自定義的函式
- 引數型別為 void(*)()
- 返回值為預設的 new_handler() 函式入口地址
- new_handler() 的定義和使用
void my_new_handler()
{
cout << "No enough memory" << endl;
}
int main(int argc, char* argv[])
{
set_new_handler(my_new_handler);
// ...
return 0;
}
程式設計實驗: new_handler 初探
公用實驗程式碼:
#include <iostream>
#include <new>
#include <cstdlib>
#include <exception>
using namespace std;
class Test
{
private:
int m_value;
public:
Test()
{
cout << "Test()" << endl;
m_value = 0;
}
~Test()
{
cout << "~Test()" << endl;
}
void* operator new (unsigned int size)
{
cout << "operator new: " << size << endl;
// return malloc(size);
return NULL; // 注意這裡! 模擬記憶體申請失敗
}
void operator delete(void* p)
{
cout << "operator delete: " << p << endl;
free(p);
}
void* operator new[] (unsigned int size)
{
cout << "operator new[]: " << size << endl;
// return malloc(size);
return NULL; // 注意這裡! 模擬記憶體申請失敗
}
void operator delete[](void* p)
{
cout << "operator delete[]: " << p << endl;
free(p);
}
};
實驗 1:不同編譯器中 new_handler() 行為
void my_new_handler()
{
cout << "void my_new_handler()" << endl;
}
void ex_func_1()
{
new_handler func = set_new_handler(my_new_handler); // 注意這裡!
try
{
cout << "func = " << func << endl;
if( func )
{
func();
}
}
catch(const bad_alloc&)
{
cout << "catch(catch bad_alloc&)" << endl;
}
}
int main()
{
ex_func_1();
return 0;
}
輸出:[g++]
func = 0
輸出:[vc++2010]
func = 00000000
輸出:[bcc]
func = 0x00401474
catch(catch bad_alloc&)
結論:
預設情況下,g++,vc++2010 並沒有提供全域性的 new_handler() 函式;
gcc 提供了全域性的 new_handler() 函式,並丟擲了 bad_alloc 異常。
實驗 2:不同編譯器 new 失敗行為
void ex_func_2()
{
Test* pt = new Test();
cout << "pt = " << pt << endl;
delete pt;
pt = new Test[5];
cout << "pt = " << pt << endl;
delete[] pt;
}
int main()
{
ex_func_2();
return 0;
}
輸出:[g++]
Test()
段錯誤
輸出:[vc++2010]
operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000
輸出:[bcc]
operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000
分析:
g++ 編譯生成的可執行檔案執行時發生段錯誤:
new 過載函式返回 NULL, 於是就在 0 地址處建立物件,呼叫建構函式。建構函式中,m_value = 0,導致段錯誤。
vc++2010:
如果 new 的過載返回 NULL,物件未建立,建構函式不會被呼叫
bcc:
如果 new 的過載返回 NULL,物件未建立,建構函式不會被呼叫
問題:如何跨編譯器統一 new 的行為, 提高程式碼的移植性呢?
解決方案【動態記憶體分配失敗時,返回空指標】
全域性範圍(不推薦)
- 重新定義 new / delete 的實現,不丟擲任何異常
- 自定義 new_handler() 函式,不丟擲任何異常
類層次範圍
- 過載 new / delete, 不丟擲任何異常
單此動態記憶體分配
- 使用 nothrow 引數,指明 new 不丟擲異常
程式設計實驗:動態記憶體申請
實驗 1: 類層次範圍
#include <iostream>
#include <new>
#include <cstdlib>
#include <exception>
using namespace std;
class Test
{
private:
int m_value;
public:
Test()
{
cout << "Test()" << endl;
m_value = 0;
}
~Test()
{
cout << "~Test()" << endl;
}
void* operator new (unsigned int size) throw() // 注意這裡!
{
cout << "operator new: " << size << endl;
// return malloc(size);
return NULL; // 注意這裡! 模擬記憶體申請失敗
}
void operator delete(void* p)
{
cout << "operator delete: " << p << endl;
free(p);
}
void* operator new[] (unsigned int size) throw() // 注意這裡!
{
cout << "operator new[]: " << size << endl;
// return malloc(size);
return NULL; // 注意這裡! 模擬記憶體申請失敗
}
void operator delete[](void* p)
{
cout << "operator delete[]: " << p << endl;
free(p);
}
};
void ex_func_2()
{
Test* pt = new Test();
cout << "pt = " << pt << endl;
delete pt;
pt = new Test[5];
cout << "pt = " << pt << endl;
delete[] pt;
}
int main()
{
ex_func_2();
return 0;
}
輸出:[g++]
operator new: 4
pt = 0
operator new[]: 24
pt = 0
輸出:[vc++2010]
operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000
輸出:[bcc]
operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000
實驗 2:單次動態記憶體分配範圍
#include <iostream>
#include <new>
#include <cstdlib>
#include <exception>
using namespace std;
void ex_func_3()
{
int* p = new(nothrow) int[10]; // 注意這裡!
cout << "p = " << p << endl;
delete p;
}
int main()
{
ex_func_3();
return 0;
}
輸出:
p = 0x8300008
實驗結論
- 不是所有的編譯器都遵循 C++ 的標準規範
- 編譯器可能重定義 new 的實現,並在實現中丟擲 bad_alloc 異常
- 編譯器的預設實現中,可能沒有設定全域性的 new_handler() 函式
- 對於移植性要求較高的程式碼,需要考慮 new 的具體細節
小結
- 不同的編譯器在動態記憶體分配上的實現細節不同
- malloc 函式在記憶體申請失敗時返回 NULL 值
new 關鍵字在記憶體申請失敗時
- 可能返回 NULL 值
- 可能丟擲 bad_alloc 異常
關於 new 的小知識點補充:
在指定的記憶體空間上建立物件(需要手動呼叫解構函式)
#include <iostream>
using namespace std;
int main()
{
int bb[2] = {0};
struct ST
{
int x;
int y;
};
ST* pt = new(bb) ST(); // 注意這裡!
pt->x = 1;
pt->y = 2;
cout << bb[0] << endl;
cout << bb[1] << endl;
pt->~ST(); // 手動呼叫解構函式
return 0;
}
輸出:
1
2
以上內容參考狄泰軟體學院系列課程,請大家保護原創!