1. 程式人生 > 其它 >C++ 第w3篇 動態記憶體申請的結果

C++ 第w3篇 動態記憶體申請的結果

技術標籤:C++c++

要尊重事實,尊重科學,尊重他人,更重要的是要尊重自己。

目錄

問題 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 異常

感謝關注,文章持續高速更新中······