C++ 第w3篇 動態記憶體申請的結果
阿新 • • 發佈:2021-01-26
要尊重事實,尊重科學,尊重他人,更重要的是要尊重自己。
目錄
- 問題 1 : 動態記憶體申請一定成功嗎?
- 記憶體申請失敗
- new語句中的異常是怎麼丟擲來的?
- new_handler() 的定義和使用
- 問題 2 : 如何跨編譯器統一 new 的行為,提高程式碼移植性?
- 程式設計實驗
- vs2010編譯器中 new 的實現分析
- 總結
問題 1 : 動態記憶體申請一定成功嗎?
常見的動態記憶體分配程式碼
C程式碼:
int* p = (int*)malloc (10 * sizeof(int));
if( p != NULL )
{
// ... ...
}
C++程式碼
int* p = new int[10];
if( p != NULL )
{
// ... ...
}
// 問題: 現代編譯器無論成功或者失敗,用不到if語句
記憶體申請失敗
malloc 函式申請失敗時返回 NULL 值
new 關鍵字申請失敗時( 根據編譯器的不同 )
- 返回 NULL 值(古代編譯器)
- 丟擲std::bad_alloc 異常(現代編譯器,如何丟擲異常?)(因此,現代編譯器無論成功或者失敗,用不到if語句)
new語句中的異常是怎麼丟擲來的?
new關鍵字在 C++ 規範中的標準行為 1
在堆空間申請足夠大的記憶體
- 成功:
- 在獲取的空間中呼叫建構函式建立物件
- 返回物件的地址
- 失敗:
- 丟擲std::bad_alloc 異常
new 關鍵字在 C++ 規範中的標準行為 2
new 在記憶體分配時(如何丟擲異常的?)
- 如果空間不足,會呼叫全域性的 new_handler()函式
- new_handler() 函式中丟擲 std::bad_alloc 異常
可以自定義 new_handler() 函式
- 處理預設的 new 記憶體分配失敗的情況
new_handler() 的定義和使用
void my_new_handler()
{
cout << "No enough memory";
cout << endl;
exit(1);
}
int main(int argc, char *argv[])
{
set_new_handler(my_new_handler);
// ... ...
return 0;
}
// 實際開發中: 一般嘗試在 my_new_handler() 做記憶體整理,期望有更多的堆空間空出來,滿足當前程式對記憶體的需求
// 比較友好的方式:記憶體不足,拋異常,異常處理。
問題 2 : 如何跨編譯器統一 new 的行為,提高程式碼移植性?
解決方案
全域性範圍( 不推薦 )
- 重新定義 new/delete 的實現,不丟擲任何異常
- 自定義 new_handler() 函式,不丟擲任何異常 (空函式,設定進去就可以了,很顯然對vs編譯器是有用的)
類層次範圍
- 過載 new/delete,不丟擲任何異常
單詞動態記憶體分配
- 使用 nothrow 引數,指明 new 不丟擲異常
程式設計實驗
動態記憶體申請
#include <iostream>
#include <new>
#include <cstdlib>
#include <exception>
using namespace std;
class Test
{
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 my_new_handler()
{
cout << "void my_new_handler()" << endl;
}
/* 證明 new_handler 函式的存在
* linux下輸出結果: func = 0
* 證明:預設情況下,G++編譯器沒有全域性的 new_handler 處理函式
* vs2010下輸出結果: func = 00000000
* BCC下輸出的結果: func = 00401474 catch(const bad_alloc&)
* 結論:不同的編譯器new的行為有些不同,不同在new失敗之後行為會不同
*/
void ex_func_1()
{
new_handler func = set_new_handler(my_new_handler); // C++中 new_handler 是一個預定義的函式指標,原來的處理函式會作為返回值返回過來
try
{
cout << "func = " << func << endl;
if( func ) // 加上這條語句,預設情況下,可能沒有處理函式
{
func();
}
}
catch(const bad_alloc&) // 想要證明 預設的 new_handler 函式要丟擲 bad_alloc 異常
{
cout << "catch(const bad_alloc&)" << endl;
}
}
/* 沒有進行異常規格說明時:
* 在linux結果: 產生段錯誤,呼叫了自己過載的new,沒有申請下空間,然後呼叫建構函式,建構函式中要對0地址處賦值,必然產生段錯誤;
* 在vs2010下結果: operator new: 4
* pt = 0
* 在BCC編譯器下結果和vs2010結果一致
* 結論: 如果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;
}
/* 如何在單次申請的時候,告訴編譯器,無論結果是啥,都不要丟擲異常。。如果申請失敗,直接返回空指標
* 結果: BCC / G++ / vs2010 三款編譯器行為一致
*/
void ex_func_3()
{
int* p = new(nothrow) int[10]; // 使用 nothrow 引數,指明 new 不丟擲異常,告訴編譯器,無論結果是啥,都不要丟擲異常
// ... ...
delete[] p;
// new 關鍵字的另外一個用法
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(); // 顯示的呼叫解構函式,為什麼這麼做? 就是因為指定了建立空間,這時就必須顯示手動的呼叫解構函式
}
int main(int argc, char *argv[])
{
// ex_func_1();
// ex_func_2();
// ex_func_3();
return 0;
}
試驗結論
- 不是所有的編譯器都遵循 C++ 的標準規範
- 編譯器可能重定義 new 的實現,並在現實中丟擲 bad_alloc 異常(異常不一定是在預設的new_handler() 中丟擲來的)
- 編譯器的預設實現中,可能沒有設定全域性的 new_handler() 函式
- 對於移植性要求較高的程式碼,需要考慮 new 的具體細節
vs2010編譯器中 new 的實現分析
/***
*new.cxx - defines C++ new routine
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
* Defines C++ new routine.
*
*******************************************************************************/
#ifdef _SYSCRT
#include <cruntime.h>
#include <crtdbg.h>
#include <malloc.h>
#include <new.h>
#include <stdlib.h>
#include <winheap.h>
#include <rtcsup.h>
#include <internal.h>
void * operator new( size_t cb )
{
void *res;
for (;;) {
// allocate memory block
res = _heap_alloc(cb);
// if successful allocation, return pointer to memory
if (res)
break;
// call installed new handler
if (!_callnewh(cb)) // 函式說明文件:如果沒有 設定 或者 沒使用(不能進行記憶體整理) 全域性的處理函式,那麼返回值為0
break; // 異常說明: 如果沒有設定全域性的處理函式new handler,那就丟擲 bad_alloc 異常
// 如果全域性的處理函式new handler沒有使記憶體增加,會跳出迴圈迴圈,最終返回值是空值
// new handler was successful -- try to allocate again
}
RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0)); // 除錯程式碼,忽略
return res;
}
#else /* _SYSCRT */
#include <cstdlib>
#include <new>
_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{ // report no memory
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
return (p);
}
/*
* Copyright (c) 1992-2002 by P.J. Plauger. ALL RIGHTS RESERVED.
* Consult your license regarding permissions and restrictions.
V3.13:0009 */
#endif /* _SYSCRT */
總結
不同的編譯器在動態記憶體分配上的實現細節不同
malloc 函式在記憶體申請失敗時返回 NULL 值
new 關鍵字在記憶體申請失敗時
- 可能返回 NULL 值
- 可能爬出 bad_alloc 異常
感謝關注,文章持續高速更新中······