青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

陳碩的Blog

C++ 工程實踐(2):不要重載全局 ::operator new()

陳碩 (giantchen_AT_gmail)

Blog.csdn.net/Solstice

 

本文只考慮 Linux x86 平臺,服務端開發(不考慮 Windows 的跨 DLL 內存分配釋放問題)。本文假定讀者知道 ::operator new() 和 ::operator delete() 是干什么的,與通常用的 new/delete 表達式有和區別和聯系,這方面的知識可參考侯捷先生的文章《池內春秋》[1],或者這篇文章

C++ 的內存管理是個老生常談的話題,我在《當析構函數遇到多線程》第 7 節“插曲:系統地避免各種指針錯誤”中簡單回顧了一些常見的問題以及在現代 C++ 中的解決辦法。基本上,按現代 C++ 的手法(RAII)來管理內存,你很難遇到什么內存方面的錯誤。“沒有錯誤”是基本要求,不代表“足夠好”。我們常常會設法優化性能,如果 profiling 表明 hot spot 在內存分配和釋放上,重載全局的 ::operator new() 和 ::operator delete() 似乎是一個一勞永逸好辦法(以下簡寫為“重載 ::operator new()”),本文試圖說明這個辦法往往行不通。

內存管理的基本要求

如果只考慮分配和釋放,內存管理基本要求是“不重不漏”:既不重復 delete,也不漏掉 delete。也就說我們常說的 new/delete 要配對,“配對”不僅是個數相等,還隱含了 new 和 delete 的調用本身要匹配,不要“東家借的東西西家還”。例如:

  • 用系統默認的 malloc() 分配的內存要交給系統默認的 free() 去釋放;
  • 用系統默認的 new 表達式創建的對象要交給系統默認的 delete 表達式去析構并釋放;
  • 用系統默認的 new[] 表達式創建的對象要交給系統默認的 delete[] 表達式去析構并釋放;
  • 用系統默認的 ::operator new() 分配的的內存要交給系統默認的 ::operator delete() 去釋放;
  • 用 placement new 創建的對象要用 placement delete (為了表述方便,姑且這么說吧)去析構(其實就是直接調用析構函數);
  • 從某個內存池 A 分配的內存要還給這個內存池。
  • 如果定制 new/delete,那么要按規矩來。見 Effective C++ 相關條款。

做到以上這些不難,是每個 C++ 開發人員的基本功。不過,如果你想重載全局的 ::operator new(),事情就麻煩了。

重載 ::operator new() 的理由

Effective C++ 第三版第 50 條列舉了定制 new/delete 的幾點理由:

  • 檢測代碼中的內存錯誤
  • 優化性能
  • 獲得內存使用的統計數據

這些都是正當的需求,文末我們將會看到,不重載 ::operator new() 也能達到同樣的目的。

::operator new() 的兩種重載方式

1. 不改變其簽名,無縫直接替換系統原有的版本,例如:

#include <new>

void* operator new(size_t size);

void operator delete(void* p);

用這種方式的重載,使用方不需要包含任何特殊的頭文件,也就是說不需要看見這兩個函數聲明。“性能優化”通常用這種方式。

2. 增加新的參數,調用時也提供這些額外的參數,例如:

void* operator new(size_t size, const char* file, int line);  // 其返回的指針必須能被普通的 ::operator delete(void*) 釋放

void operator delete(void* p, const char* file, int line);  // 這個函數只在析構函數拋異常的情況下才會被調用

然后用的時候是

Foo* p = new (__FILE, __LINE__) Foo;  // 這樣能跟蹤是哪個文件哪一行代碼分配的內存

我們也可以用宏替換 new 來節省打字。用這第二種方式重載,使用方需要看到這兩個函數聲明,也就是說要主動包含你提供的頭文件。“檢測內存錯誤”和“統計內存使用情況”通常會用這種方式重載。當然,這不是絕對的。

 

在學習 C++ 的階段,每個人都可以寫個一兩百行的程序來驗證教科書上的說法,重載 ::operator new() 在這樣的玩具程序里邊不會造成什么麻煩。

不過,我認為在現實的產品開發中,重載 ::operator new() 乃是下策,我們有更簡單安全的辦法來到達以上目標。

現實的開發環境

作為 C++ 應用程序的開發人員,在編寫稍具規模的程序時,我們通常會用到一些 library。我們可以根據 library 的提供方把它們大致分為這么幾大類:

  1. C 語言的標準庫,也包括 Linux 編程環境提供的 Posix 系列函數。
  2. 第三方的 C 語言庫,例如 OpenSSL。
  3. C++ 語言的標準庫,主要是 STL。(我想沒有人在產品中使用 IOStream 吧?)
  4. 第三方的通用 C++ 庫,例如 Boost.Regex,或者某款 XML 庫。
  5. 公司其他團隊的人開發的內部基礎 C++ 庫,比如網絡通信和日志等基礎設施。
  6. 本項目組的同事自己開發的針對本應用的基礎庫,比如某三維模型的仿射變換模塊。

在使用這些 library 的時候,不可避免地要在各個 library 之間交換數據。比方說 library A 的輸出作為 library B 的輸入,而 library A 的輸出本身常常會用到動態分配的內存(比如 std::vector<double>)。

如果所有的 C++ library 都用同一套內存分配器(就是系統默認的 new/delete ),那么內存的釋放就很方便,直接交給 delete 去釋放就行。如果不是這樣,那就得時時刻刻記住“這一塊內存是屬于哪個分配器,是系統默認的還是我們定制的,釋放的時候不要還錯了地方”。

(由于 C 語言不像 C++ 一樣提過了那么多的定制性,C library 通常都會默認直接用 malloc/free 來分配和釋放內存,不存在上面提到的“內存還錯地方”問題。或者有的考慮更全面的 C library 會讓你注冊兩個函數,用于它內部分配和釋放內存,這就就能完全掌控該 library 的內存使用。這種依賴注入的方式在 C++ 里變得花哨而無用,見陳碩寫的《C++ 標準庫中的allocator是多余的》。)

但是,如果重載了 ::operator new(),事情恐怕就沒有這么簡單了。

重載 ::operator new() 的困境

首先,重載 ::operator new() 不會給 C 語言的庫帶來任何麻煩,當然,重載它得到的三點好處也無法讓 C 語言的庫享受到。

以下僅考慮 C++ library 和 C++ 主程序。

規則 1:絕對不能在 library 里重載 ::operator new()

如果你是某個 library 的作者,你的 library 要提供給別人使用,那么你無權重載全局 ::operator new(size_t) (注意這是上面提到的第一種重載方式),因為這非常具有侵略性:任何用到你的 library 的程序都被迫使用了你重載的 ::operator new(),而別人很可能不愿意這么做。另外,如果有兩個 library 都試圖重載 ::operator new(size_t),那么它們會打架,我估計會發生 duplicated symbol link error。干脆,作為 library 的編寫者,大家都不要重載 ::operator new(size_t) 好了。

那么第二種重載方式呢?首先,::operator new(size_t size, const char* file, int line) 這種方式得到的 void* 指針必須同時能被 ::operator delete(void*) 和 ::operator delete(void* p, const char* file, int line) 這兩個函數釋放。這時候你需要決定,你的 ::operator new(size_t size, const char* file, int line) 返回的指針是不是兼容系統默認的 ::operator delete(void*)。

  • 如果不兼容(也就是說不能用系統默認的 ::operator delete(void*) 來釋放內存),那么你得重載 ::operator delete(void*),讓它的行為與你的 operator new(size_t size, const char* file, int line) 匹配。一旦你決定重載 ::operator delete(void*),那么你必須重載 ::operator new(size_t),這就回到了情況 1:你無權重載全局 ::operator new(size_t)。
  • 如果選擇兼容系統默認的 ::operator delete(void*),那么你在 operator new(size_t size, const char* file, int line) 里能做的事情非常有限,比方說你不能額外動態分配內存來做 house keeping 或保存統計數據(無論顯示還是隱式),因為系統默認的 ::operator delete(void*) 不會釋放你額外分配的內存。(這里隱式分配內存指的是往 std::map<> 這樣的容器里添加元素。)

看到這里估計很多人已經暈了,但這還沒完。

其次,在 library 里重載 operator new(size_t size, const char* file, int line) 還涉及到你的重載要不要暴露給 library 的使用者(其他 library 或主程序)。這里“暴露”有兩層意思:1) 包含你的頭文件的代碼會不會用你重載的 ::operator new(),2) 重載之后的 ::operator new() 分配的內存能不能在你的 library 之外被安全地釋放。如果不行,那么你是不是要暴露某個接口函數來讓使用者安全地釋放內存?或者返回 shared_ptr ,利用其“捕獲”deleter 的特性?聽上去好像挺復雜?這里就不一一展開討論了,總之,作為 library 的作者,絕對不要動“重載 operator new()”的念頭。

事實 2:在主程序里重載 ::operator new() 作用不大

這不是一條規則,而是我試圖說明這么做沒有多大意義。

如果用第一種方式重載全局 ::operator new(size_t),會影響本程序用到的所有 C++ library,這么做或許不會有什么問題,不過我建議你使用下一節介紹的更簡單的“替代辦法”。

如果用第二種方式重載 ::operator new(size_t size, const char* file, int line),那么你的行為是否惠及本程序用到的其他 C++ library 呢?比方說你要不要統計 C++ library 中的內存使用情況?如果某個 library 會返回它自己用 new 分配的內存和對象,讓你用完之后自己釋放,那么是否打算對錯誤釋放內存做檢查?

C++ library 從代碼組織上有兩種形式:1) 以頭文件方式提供(如以 STL 和 Boost 為代表的模板庫);2) 以頭文件+二進制庫文件方式提供(大多數非模板庫以此方式發布)。

對于純以頭文件方式實現的 library,那么你可以在你的程序的每個 .cpp 文件的第一行包含重載 ::operator new 的頭文件,這樣程序里用到的其他 C++ library 也會轉而使用你的 ::operator new 來分配內存。當然這是一種相當有侵略性的做法,如果運氣好,編譯和運行都沒問題;如果運氣差一點,可能會遇到編譯錯誤,這其實還不算壞事;運氣更差一點,編譯沒有錯誤,運行的時候時不時出現非法訪問,導致 segment fault;或者在某些情況下你定制的分配策略與 library 有沖突,內存數據損壞,出現莫名其妙的行為。

對于以庫文件方式實現的 library,這么做并不能讓其受惠,因為 library 的源文件已經編譯成了二進制代碼,它不會調用你新重載的 ::operator new(想想看,已經編譯的二進制代碼怎么可能提供額外的 new (__FILE__, __LINE__) 參數呢?)更麻煩的是,如果某些頭文件有 inline function,還會引起詭異的“串擾”。即 library 有的部分用了你的分配器,有的部分用了系統默認的分配器,然后在釋放內存的時候沒有給對地方,造成分配器的數據結構被破壞。

總之,第二種重載方式看似功能更豐富,但其實與程序里使用的其他 C++ library 很難無縫配合。

 

綜上,對于現實生活中的 C++ 項目,重載 ::operator new() 幾乎沒有用武之地,因為很難處理好與程序所用的 C++ library 的關系,畢竟大多數 library 在設計的時候沒有考慮到你會重載 ::operator new() 并強塞給它。

如果確實需要定制內存分配,該如何辦?

替代辦法

很簡單,替換 malloc。如果需要,直接從 malloc 層面入手,通過 LD_PRELOAD 來加載一個 .so,其中有 malloc/free 的替代實現(drop-in replacement),這樣能同時為 C 和 C++ 代碼服務,而且避免 C++ 重載 ::operator new() 的陰暗角落。

對于“檢測內存錯誤”這一用法,我們可以用 valgrind 或者 dmalloc 或者 efence 來達到相同的目的,專業的除錯工具比自己山寨一個內存檢查器要靠譜。

對于“統計內存使用數據”,替換 malloc 同樣能得到足夠的信息,因為我們可以用 backtrace() 函數來獲得調用棧,這比 new (__FILE__, __LINE__) 的信息更豐富。比方說你通過分析 (__FILE__, __LINE__) 發現 std::string 大量分配釋放內存,有超出預期的開銷,但是你卻不知道代碼里哪一部分在反復創建和銷毀 std::string 對象,因為 (__FILE__, __LINE__) 只能告訴你最內層的調用函數。用 backtrace() 能找到真正的發起調用者。

對于“性能優化”這一用法,我認為這目前的多線程開發中,自己實現一個能打敗系統默認的 malloc 的內存分配器是不現實的。一個通用的內存分配器本來就有相當的難度,為多線程程序實現一個安全和高效的通用(全局)內存分配器超出了一般開發人員的能力。不如使用現有的針對多核多線程優化的 malloc,例如 Google tcmalloc 和 Intel TBB 2.2 里的內存分配器。好在這些 allocator 都不是侵入式的,也無須重載 ::operator new()。

為單獨的 class 重載 operator new() 有問題嗎?

與全局 ::operator new() 不同,per-class operator new() 和 operator delete () 的影響面要小得多,它只影響本 class 及其派生類。似乎重載 member operator new() 是可行的。我對此持反對態度。

如果一個 class Node 需要重載 member operator new(),說明它用到了特殊的內存分配策略,常見的情況是使用了內存池或對象池。我寧愿把這一事實明顯地擺出來,而不是改變 new Node 的默認行為。具體地說,是用 factory 來創建對象,比如 static Node* Node::createNode() 或者 static shared_ptr<Node> Node::createNode();。

這可以歸結為最小驚訝原則:如果我在代碼里讀到 Node* p = new Node,我會認為它在 heap 上分配了內存,如果 Node class 重載了 member operator new(),那么我要事先仔細閱讀 node.h 才能發現其實這行代碼使用了私有的內存池。為什么不寫得明確一點呢?寫成 Node* p = Node::createNode(),那么我能猜到 Node::createNode() 肯定做了什么與 new Node 不一樣的事情,免得將來大吃一驚。

The Zen of Python 說 explicit is better than implicit,我深信不疑。

 

總結:重載 ::operator new() 或許在某些臨時的場合能應個急,但是不應該作為一種策略來使用。如果需要,我們可以從 malloc 層面入手,徹底而全面地替換內存分配器。

參考文獻:

[1] 侯捷,《池內春秋—— Memory Pool 的設計哲學與無痛運用》,《程序員》2002 年第 9 期。

posted on 2011-02-22 01:02 陳碩 閱讀(20211) 評論(12)  編輯 收藏 引用

評論

# re: C++ 工程實踐(2):不要重載全局 ::operator new()[未登錄] 2011-02-22 07:10 欲三更

不同模塊的內存問題,我覺得這簡直是一個c++設計欠考慮的地方。就算是不重載new也不見得沒問題。只要不是自己源碼編譯的模塊都不保險。  回復  更多評論   

# re: C++ 工程實踐(2):不要重載全局 ::operator new() 2011-02-22 10:00 空明流轉

allocator的作用很重要。  回復  更多評論   

# re: C++ 工程實踐(2):不要重載全局 ::operator new() 2011-02-22 13:41 陳梓瀚(vczh)

VC++內置內存跟蹤工具  回復  更多評論   

# re: C++ 工程實踐(2):不要重載全局 ::operator new() 2011-02-22 15:15 泛泛

不錯~~~~~~~~~~~~~~~~~~  回復  更多評論   

# re: C++ 工程實踐(2):不要重載全局 ::operator new()[未登錄] 2011-02-22 16:01 ccc

寫的很好  回復  更多評論   

# re: C++ 工程實踐(2):不要重載全局 ::operator new() 2011-02-22 17:02 zwp

受教了:)  回復  更多評論   

# re: C++ 工程實踐(2):不要重載全局 ::operator new()[未登錄] 2011-02-22 21:10 xxx

被同事的重載operator new害過的人路過。  回復  更多評論   

# Term Paper Writing Service 2011-03-12 13:52 Term Paper Writing Service

Writing Services to help students write essays, term papers, research papers. Buy custom research essay, custom term papers, thesis and dissertation writing.
  回復  更多評論   

# re: C++ 工程實踐(2):不要重載全局 ::operator new()[未登錄] 2011-12-22 14:09 Chipset

內存管理系統的事情,對C++要求這么高,是不是太過分了。new就是一個殼,調了API。當然,最好別重載全局new,局部的我看也最好別重載。一般程序new就足矣,多CPU并行和吞吐量的程序,只能自己來管內存,否則慢也就罷了,還出一堆碎片。

系統管理內存都做不到完美,指望語言級別依賴于實現的new能做好?看看系統內核每次升級,有多少改動發生在虛擬內存管理器...  回復  更多評論   

# re: C++ 工程實踐(2):不要重載全局 ::operator new() 2012-01-13 21:32 幻の上帝

不重載class scope operator new這種自我閹割的限制性手法居然成了符合最小驚訝?呵呵。我倒是挺愿意打那些既沒學好基本語義又懶得看清楚文檔的家伙們的臉的。
Explicit is better than implicit,雖然原意是沒什么錯,嘛,某些人就是擺脫不了用得羅嗦的原罪。要在這方面鼓吹這個是拉攏BASIC用戶么。
  回復  更多評論   

# re: C++ 工程實踐(2):不要重載全局 ::operator new() 2013-07-12 18:15 thomax

great content  回復  更多評論   

# re: C++ 工程實踐(2):不要重載全局 ::operator new() 2013-07-12 20:17 david parker

nice post  回復  更多評論   


只有注冊用戶登錄后才能發表評論。
網站導航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


<2011年12月>
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

導航

統計

常用鏈接

隨筆分類

隨筆檔案

相冊

搜索

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            91久久国产综合久久| 欧美视频1区| 裸体一区二区| 久久久久久91香蕉国产| 午夜精品在线| 久久久最新网址| 免费毛片一区二区三区久久久| 久久久国产精品一区| 久久麻豆一区二区| 欧美成人三级在线| 亚洲精品乱码久久久久久久久| 亚洲黄色一区二区三区| 99av国产精品欲麻豆| 亚洲制服av| 久久精品首页| 欧美黑人多人双交| 国产精品一区二区久久国产| 一区二区三区在线观看国产| 亚洲国产欧美久久| 亚洲欧美日韩天堂| 麻豆成人在线观看| 亚洲毛片视频| 久久久精品一区| 欧美日韩色一区| 娇妻被交换粗又大又硬视频欧美| 99re这里只有精品6| 久久黄色小说| 亚洲理论在线| 久久久久久亚洲综合影院红桃| 欧美第十八页| 国产欧美一区在线| 一区二区电影免费观看| 欧美一区二区三区久久精品茉莉花| 蜜桃av一区二区| 亚洲免费在线| 欧美精品一区在线播放| 黄色成人免费网站| 小黄鸭精品aⅴ导航网站入口| 欧美成人免费播放| 欧美在线观看一区| 国产精品美女黄网| 一区二区三区视频免费在线观看 | 久久精品一区蜜桃臀影院| 欧美日韩免费观看一区二区三区| 激情六月婷婷久久| 欧美国产精品va在线观看| 欧美国产激情二区三区| 亚洲视频在线观看一区| 欧美一区国产二区| 亚洲天天影视| 一二三区精品| 你懂的网址国产 欧美| 宅男噜噜噜66一区二区| 欧美日本不卡视频| 日韩视频一区二区三区在线播放| 久久免费国产精品1| 羞羞漫画18久久大片| 国产精品自拍小视频| 久久久之久亚州精品露出| 欧美自拍偷拍午夜视频| 精品电影一区| 国产精品久久久久久久久久久久久| 在线亚洲伦理| 午夜精品久久久久| 亚久久调教视频| 国产一区二区精品丝袜| 在线综合欧美| 亚洲高清自拍| 99精品国产在热久久婷婷| 国产精品中文字幕欧美| 欧美黄色网络| 国产亚洲欧美日韩在线一区| 欧美国产精品久久| 国产日韩av一区二区| 亚洲精品一区二区三区四区高清| 国产精品女人久久久久久| 亚洲一区二区网站| 久久久综合网| 一区二区日韩免费看| 欧美黄网免费在线观看| 亚洲欧美在线磁力| 欧美日韩精品欧美日韩精品| 亚洲欧美日韩精品久久亚洲区| 久久久一二三| 久久网站热最新地址| 国产精品无码永久免费888| 亚洲高清免费在线| 国产一区二区按摩在线观看| 亚洲天堂男人| 欧美亚洲综合网| 欧美日韩在线第一页| 99这里只有久久精品视频| 韩日欧美一区| 欧美日本精品| 亚洲欧美日韩一区在线| 久久久久久久综合狠狠综合| 国产欧美精品一区| 久久久久看片| 国产精品99久久久久久有的能看| 欧美一区二区黄| 91久久综合| 国产欧亚日韩视频| 欧美激情一区三区| 亚洲伊人伊色伊影伊综合网| 久久九九热re6这里有精品| 亚洲国产专区校园欧美| 国产精品草莓在线免费观看| 久久国产精品久久久久久| 亚洲精品黄色| 欧美阿v一级看视频| 午夜国产欧美理论在线播放| 影音先锋亚洲一区| 国产情人节一区| 国产精品久久久99| 欧美成人午夜剧场免费观看| 亚洲免费在线观看视频| 伊人婷婷久久| 国产精品亚发布| 欧美国产三区| 欧美极品一区| 欧美另类高清视频在线| 久久视频国产精品免费视频在线| 一本色道久久综合精品竹菊| 99国产欧美久久久精品| 亚洲乱码国产乱码精品精天堂| 免费久久99精品国产自| 媚黑女一区二区| 欧美1区2区3区| 日韩亚洲一区二区| 一本色道久久综合狠狠躁篇的优点| 99天天综合性| 欧美在线视频网站| 欧美+亚洲+精品+三区| 欧美极品在线观看| 国产精品一区二区你懂的| 欧美午夜精品久久久久久人妖 | 久久久免费精品视频| 久久资源av| 欧美日韩精品福利| 国产视频一区在线观看一区免费| 国产欧美日韩不卡免费| 亚洲成人影音| 午夜精品999| 91久久线看在观草草青青| 亚洲最新色图| 欧美在线观看一二区| 欧美日韩国语| 亚洲电影网站| 亚洲人成免费| 伊人久久噜噜噜躁狠狠躁| 久久疯狂做爰流白浆xx| 欧美另类69精品久久久久9999| 欧美日韩一区二区三区在线| 伊人久久综合97精品| 久久久久久精| 日韩天堂av| 亚洲国产一二三| 久久伊人一区二区| 国产一区二区三区高清| 亚洲在线免费| 99国产精品久久久久久久成人热| 久久另类ts人妖一区二区| 国产一区二区在线免费观看| 亚洲天堂网在线观看| 亚洲成人在线网站| 久久综合色8888| 亚洲黄色在线| 亚洲国产精品精华液2区45| 久久国产免费| 亚洲第一福利社区| 麻豆精品视频| 欧美午夜视频| 久久综合久久久久88| 美女久久网站| 亚洲性xxxx| 久久久久久电影| 国产精品99久久久久久人| 亚洲尤物视频网| 亚洲精品一区在线| 午夜亚洲影视| 亚洲曰本av电影| 久久亚洲精品伦理| 一区二区三区视频在线观看| 亚洲精品影院| 亚洲欧洲日产国产网站| 亚洲一级在线观看| 日韩特黄影片| 久久综合国产精品| 久久精品二区三区| 欧美性久久久| 在线午夜精品| 亚洲影院免费观看| 欧美激情在线狂野欧美精品| 久久精品综合一区| 国产欧美韩日| 欧美在线观看网站| 欧美一区二区网站| 国产视频亚洲| 老**午夜毛片一区二区三区| 欧美一区二区三区在线看|