[翻譯] Effective C++, 3rd Edition, Item 50: 領會何時替換 new 和 delete 才有意義

來源:互聯網
上載者:User

Item 50: 領會何時替換 new 和 delete 才有意義

作者:Scott Meyers

譯者:fatalerror99 (iTePub's Nirvana)

發布:http://blog.csdn.net/fatalerror99/

讓我們先回顧一下基礎。為什麼有些人想要替換編譯器提供的 operator new 或 operator delete 版本呢?有三個最主要的原因:

  • 為了監測使用錯誤。對由 new 產生的記憶體沒有實行 delete 會導致記憶體流失。在 new 出的記憶體上實行多於一次的 delete 會引發未定義行為。如果 operator new 儲存一個已指派地址的列表,而 operator delete 從這個列表中移除地址,這樣就很容易監測到上述使用錯誤。同樣,某種編程錯誤會導致 data overruns(資料上溢)(在一個已指派塊的末端之後寫入)和 underruns(下溢)(在一個已指派塊的始端之前寫入)。在對於客戶可用的記憶體的之前和之後,自訂 operator news 可以跨越分配塊,在這些空間放置已知的位元組模式 ("signatures")。operator deletes 會去檢查這些 signatures 是否依舊保持原樣。如果不是,在這個分配塊的生存期間的某個時刻發生了一個上溢或者下溢,而且 operator deletes 可以記錄這件事以及那個討厭的指標的值。
  • 為了提升效能。由編譯器載入的 operator new 和 operator delete 版本是為了多種用途而設計的。它們必須被長時間啟動並執行程式(例如,web servers),接受,但是,它們也必須被已耗用時間少於一秒的程式接受。它們必須處理大記憶體塊,小記憶體塊,以及兩者混合的請求序列。它們必須適應廣泛的分配模式,從存在於整個程式的持續期間的少數幾個區塊的動態分配到大量短壽命 objects 的持續不斷的分配和釋放。它們必須為堆片段化負責,對這個過程,如果不進行控制,最終會導致不能滿足對大記憶體塊的請求,即使有足夠的自由記憶體分布在大量小塊中。

由於記憶體管理器的特定需求,由編譯器載入的 operator news 和 operator deletes 採取了 middle-of-the-road strategy(中間路線策略)不值得大驚小怪。它們的工作對每一個人來說都說得過去,但是對誰都不是最合適的。如果你對你的程式的動態記憶體的應用模式有充分的理解,你可能經常發現 operator new 和 operator delete 的自訂版本勝於預設版本。對於“勝於”,我的意思是它們運行更快——有時會有數量級的提升——而且它們需要更少的記憶體——最高會少於 50%。對於某些(儘管不意味著全部)應用程式,用自訂版本取代普通的 new 和 delete 是獲得重大效能提升的一個簡單方法。

  • 為了收集使用方法的統計資料。在一頭紮入編寫自訂 news 和 deletes 的道路之前,收集一下你的軟體如何使用動態記憶體的資訊還是比較明智的。被分配區塊大小的分布如何?生存期的分布如何?它們的分配和釋放的順序是趨向於 FIFO ("first in, first out")(“先進先出”),或者 LIFO ("last in, first out")(“後進先出”)的順序,還是某種接近於隨機的順序?使用模式會隨著時間而變化嗎?例如,你的軟體是不是在不同的運行階段有不同的分配/釋放模式?在任一時間內使用中的動態分配記憶體的最大值(也就是說,它的“最高水位”)是多少?operator new 和 operator delete 的自訂版本使得收集這類資訊變得容易。

在概念上,編寫一個自訂 operator new 相當簡單。例如,這是一個便於 under- 和 overruns 的檢測的 global operator new 的主要部分。這裡有很多小麻煩,但是我們馬上就來關注一下它們。

static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;

// this code has several flaws—see below
void* operator new(std::size_t size) throw(std::bad_alloc)
{
  using namespace std;

  size_t realSize = size + 2 * sizeof(int);    // increase size of request so2
                                               // signatures will also fit inside

  void *pMem = malloc(realSize);               // call malloc to get theactual
  if (!pMem) throw bad_alloc();                // memory

  // write signature into first and last parts of the memory
  *(static_cast<int*>(pMem)) = signature;
  *(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize-sizeof(int))) =
  signature;

  // return a pointer to the memory just past the first signature
  return static_cast<Byte*>(pMem) + sizeof(int);
}

這個 operator new 的大多數缺陷都與它沒有遵循叫這個名字的函數的 C++ 慣例有關。例如,Item 51 闡明:所有的 operator new 都應該包含一個調用 new-handling function 的迴圈,但是這裡沒有。但是,Item 51 正是專用於這樣的慣例,所以我在這裡忽略它們。我現在要關注一個更微妙的問題:alignment(排列對齊)。

很多電腦架構要求特定類型的資料要放置在記憶體中具有特定性質的地址中。例如,一種架構可能要求 pointers(指標)要出現在四的倍數的地址上(也就是說,按照四位元組對齊)或者 doubles(雙精確度浮點型)必須出現在八的倍數的地址上(也就是說,按照八位元組對齊)。不遵守這樣的約束會導致 hardware exceptions at runtime(運行時硬體異常)。其它的架構可能會寬容一些,但是如果滿足了排列對齊的次序會得到更好的效能。例如,在 Intel x86 架構上 doubles(雙精確度浮點型)可以按照任意位元組分界排列,但是如果他們按照八位元組對齊,訪問速度會快得多。

alignment(排列對齊)在這裡有重大意義,因為 C++ 要求所有的 operator news 返回適合任何資料類型的排列的指標。malloc 也工作於同樣的要求下,所以,讓 operator new 返回它從 malloc 得到的指標是安全的。然而,在上面的 operator news 中,我們沒有返回我們從 malloc 得到的指標,我們返回的指標比我們從 malloc 得到的指標位移了一個 int 大小。無法保證這是安全的!如果客戶調用 operator new 為一個 double(或者,如果我們正在編寫 operator new[],一個 doubles 的數組)申請足夠的記憶體,而且我們正在運行一台 ints 是四個位元組大小而 doubles 需要八位元組對齊的機器,我們就可能返回對齊不恰當的指標。這可以導致程式崩潰。或者,它只是導致運行速度變慢。無論哪種情況,這或許都不是我們想要的。

像 alignment(排列對齊)這樣的細節可以用於區分專業品質的記憶體管理器和那些由需要解決其它任務而心煩意亂的程式員匆匆拼湊出來的東西。編寫一個幾乎能工作的自訂記憶體管理器相當容易。編寫一個工作得很好的要困難得多。作為一個一般規則,我建議你不要致力於此,除非你不得不做。

很多情況下,你並非不得不做。有些編譯器提供選項開關用為它們的 memory management functions(記憶體管理函數)開啟調試和記錄的功能。快速探索一下你的編譯器的文檔也許可以打消你編寫 new 和 delete 的念頭。在很多平台上,商用產品可以替代隨編譯器提供的 memory management functions(記憶體管理函數)。為了利用它們的增強功能以及(或許會有的)更好的效能,你需要做的全部就是重新連結。(當然,你還必須把它們買回來。)

另一個選擇是開源的記憶體管理器。它們可用於多種平台,所以你可以下載並試用。出自於 Boost(參見 Item 55)的 Pool library 就是一個這樣的開源分配器。Pool library 提供了針對自訂記憶體管理能提供協助的最通常的情況之一(大數量 small objects(小對象)的分配)進行了調諧的分配器。很多 C++ 書籍,包括本書的早期版本,展示了一個 high-performance small-object allocator(高效能小對象分配器)的代碼,但是它們通常忽略了可移植性和排列對齊的考慮以及安全執行緒等等諸如此類的麻煩的細節。真正的庫會注意用健壯得多的代碼。即使你決定編寫你自己的 news 和 deletes,看一下開源版本很可能會為你提供對“區分幾乎起作用和真正起作用”的容易忽略的細節的洞察力。(已知 alignment(排列對齊)就是一個這樣的細節,值得一提的是,TR1(參見 Item 54)包含了對已發現的類型特定的排列對齊要求的支援。)

這個 Item 的主題是瞭解何時替換 new 和 delete 的預設版本(無論是基於全域的還是 per-class 的)才有意義。我們現在應該比前面更詳細地總結一下時機問題。

  • 為了監測使用錯誤(如前)。
  • 為了收集有關動態分配記憶體的使用的統計資料(如前)。
  • 為了提升分配和回收的速度。general-purpose allocators(通用目的的分配器)通常(雖然不總是)比自訂版本慢很多,特別是如果自訂版本是為某種特定類型的 objects 專門設計的。class-specific allocators(類專用分配器)是 fixed-size allocators(固定大小分配器)(就像 Boost 的 Pool library 所提供的那些)的一種典範應用。如果你的程式是 single-threaded(單線程)的,而你的編譯器預設的記憶體管理常式是 thread-safe(安全執行緒)的,通過編寫 thread-unsafe allocators(非安全執行緒分配器)你可以獲得相當的速度提升。當然,在得出 operator new 和 operator delete 對速度提升有價值的結論之前,確實測定你的程式以保證這些函數是真正的瓶頸。
  • 為了減少預設記憶體管理的空間成本。general-purpose memory managers(通用目的的記憶體管理器)通常(雖然不總是)不僅比自訂版本慢,而且還經常使用更多的記憶體。這是因為它們經常為每個已指派區塊招致某些成本。針對 small objects(小對象)調諧的分配器(諸如 Boost 的 Pool library 中的那些)從根本上消除了這樣的成本。
  • 為了調整預設分配器不適當的排列對齊。就像我前面提到的,在 x86 架構上,當 doubles 按照八位元組對齊時訪問速度是最快的。哎呀,有些隨編譯器提供的 operator news 不能保證 doubles 的動態分配按照八位元組對齊。在這種情況下,用保證按照八位元組對齊的 operator new 替換掉預設版本,可以使程式效能得到較大提升。
  • 為了聚集相關的 objects,使它們彼此靠近。如果你知道特定的 data structures(資料結構)通常會在一起使用,而且你想將在這些資料上工作時的頁錯誤頻率降到最低,那麼為這些 data structures(資料結構)建立一個獨立的 heap(堆)以便讓它們儘可能地聚集在不多的幾個頁上就是有意義的。new 和 delete 的 placement versions(參見 Item 52)使得完成這樣的聚整合為可能。
  • 為了獲得不同尋常的行為。有時你想讓 operators new 和 delete 做一些編譯器裝備版本沒有提供的事情。例如,你可能想在共用記憶體中分配和回收區塊,但是只能通過一個 C API 來管理那片記憶體。編寫 new 的 delete 的自訂版本(或許是 placement versions——再次參見 Item 52)允許你用 C++ 衣服來遮住那個 C API。作為另一個例子,你可以編寫一個自訂的 operator delete 用 zeros 複寫被回收的記憶體以提高應用程式資料的安全性。

Things to Remember

  • 有很多正當的編寫 new 和 delete 的自訂版本的理由,包括改進效能,調試 heap(堆)用法錯誤,以及收集 heap(堆)用法資訊。
     

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.