【C++】 外傳篇 3_動態內存申請的結果

動態內存的結果

問題: 動態內存申請必定成功嗎?ios

  • 常見的動態內存分配代碼

C 代碼: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 關鍵字申請失敗時(根據編譯器不一樣)spa

      • 返回 NULL 值 (古代)
      • 拋出 std::bad_alloc 異常 (現代)

問題: new 語句中的異常是怎麼拋出來的呢?指針

  • new 關鍵字在 C++ 規範中的標準行爲code

    • 在堆空間申請足夠的內存對象

      • 成功:內存

        • 在獲取的空間中調用構造函數建立對象
        • 返回對象的地址
      • 失敗編譯器

        • 拋出 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

以上內容參考狄泰軟件學院系列課程,請你們保護原創!

相關文章
相關標籤/搜索