天天看點

C/C++程式設計:了解new和delete的合理替換時機

問題:為什麼會想要替換編譯器提供的operator new或者operator delete呢?
  • 用來檢測運用上的錯誤。如果将“new所得記憶體”delete掉卻不幸失敗,會導緻記憶體洩漏。如果在“new所得記憶體”上多次delete程式會崩潰。此外各種各樣的程式設計錯誤可能導緻資料"overruns"(寫入點在配置設定區尾端之後)或者"underruns"(寫入點在配置設定區起始點之前)。如果我們自行定義一個operator new,就可以超額配置設定記憶體,以額外空間(位于客戶所得區塊之前或之後)放置特定的byte patterns(即簽名)。operator delete就可以檢查上訴簽名是否原封不動,如果改變了就表示配置設定區在某個生命時間段發生了orerrun或者underrun,這時候operator delete可以log這個事實和這個惹是生非的指針
  • 為了強化效能。編譯器所帶的operator new和operator delete主要用于一般目的,它們不但可以被長時間執行的程式接受,也可以被執行時間小于一秒的程式接受。它們必須處理一系列需求,包括大塊記憶體、小塊記憶體、大小混合型記憶體。它們必須接納各種配置設定形态,範圍從程式存活期間的少量區塊動态配置設定,到大數量短命對象的持續配置設定和歸還。它們必須考慮碎片問題,這最終會導緻程式無法滿足大區塊要求… 現實存在這麼多問題,是以編譯器提供的operator new和operator delete隻是一般化的,不會針對任何人有最佳表現。對某些應用程式而言,将編譯器自帶的new和delete替換為定制版本,可以效率更高,空間更少。
    • 為了增加配置設定和歸還的速度。
    • 為了降低預設記憶體管理器帶來的額外開銷
  • 為了收集使用上的資料。對heap運用錯誤進行調試、收集heap使用資訊等

看個例子。下面是個初階段的global operator new,促進并協助檢查“overruns”或者“underruns”

// 無法通過編譯
static const int signature = 0xDEADBEFE;
typedef unsigned char Byte;

// 錯誤1:所有operator new都應該内含一個循環,反複調用每個new-handler函數
// 錯誤2:對齊
void * operator new(std::size_t size) throw(std::bad_alloc){
    using namespace std;
    size_t realSize = size + 2 * sizeof(int); // 增加大小,使得其能夠塞入兩個signature 

    void *pMem = malloc(realSize);
    if(!pMem){
        throw bad_alloc();
    }

	// 将signature 寫入記憶體的最前和最後區域
    *(static_cast<int *>(pMem)) = signature; 
    *(reinterpret_cast<int *>(static_cast<Byte *>pMem)
       + realSize - sizeof(int)) = signature;

	// 傳回值指針,指向第一個signature 之後的記憶體位置
    return static_cast<Byte *>(pMem) + sizeof(int);
}
           

有關對齊:C++要求所有的operator new傳回的指針都有适當的對齊(取決于資料類型)。malloc就是在這樣的要求下工作的,是以令operator new傳回一個得自malloc的指針是安全的。然而上面的operator new傳回的是一個得自malloc且偏移一個int大小的指針。沒人能夠保證它的安全!

c++

繼續閱讀