Solmyr和Zero的故事 —— 記憶體,最後一塊 (轉)
:namespace prefix = o ns = "urn:schemas--com::office" />
“兄弟,怎麼了。”Zero問。
“Zero是我們這組的主員,他懂得很多,人長的也帥,很討女孩子的歡心。甚至連美工組的Lili(長得比孫燕姿還好看)也暗戀他。”。Solmyr一邊想,一邊說,“老大,你看,又當機了。為什麼我每次用動態記憶體超過10次,就crash了呢?”
“我知道你原來是程式設計師,也許到現在,你還念念不忘那GC (垃圾收集機制,我說還不如叫上帝也哭泣-God Cry),可你要知道,你現在是在用C++,重要的是。”
“效率,我……”忽然,Solmyr覺得嘴角邊似乎有液體流過。那是Solmyr的口水。每次提到效率,Solmyr總要流口水,就像看到漂亮MM,Solmyr要噴鼻血一樣。
“C++中,關於動態記憶體的是new and delete。”
“我知道,”Solmyr急於表現自己,想證明自己對C++並不是一無所知,“我正在讀tt Meyer的More Effective C++。在C++中,new operator是C++內建的行為。任何人(也許除了Bjarne Stroustrup)都無法改變。new operator先一個名為operator new的動態申請記憶體。標準就像這樣:
void* operator new(size_t size);
然後在傳回的void*指標上進行構造的行為。而delete operator則先析構,然後呼叫名為operator delete的函式,標準形式就像這樣:
void operator delete(void* pToDeAlloc)
// GotW中說即使在指標引數後加上size_t size,仍然是標準形式。
// size_t size的作用是檢查所要卸除的記憶體是否是期望的大小,如果在類層次中定義的話,只要基類是virtual destructor,那麼size可以// 確保是正確大小。
而placement new……。”
“唔,說得不錯,有進步。關於placement new/delete,先知Meyer[1]有詳細的論述。原來的placement new僅僅是這樣:
void* operator new(size_t, void* pMem) { return pMem; }
‘ 隨著時間過去,任何‘要求額外引數’的 operator new 版本,也都漸漸採用 placement new 這個術語。事實上這個術語已經被銘記於 C++ 標準規格中。因此,當 C++ 程式設計師談到所謂的 placement new 函式,他們所談的可能是上述那個‘需要額外一個 void* 引數,用以指出物件置於何處’的版本,但也可能是指那些‘所需引數比單一而必要之 size_t 引數更多’的任何 operator new 版本,包括上述函式,也包括其他‘引數更多’的 operator new 函式。’——引自[1]。
現在我們來考慮一個問題,如果在operator new結束申請記憶體後,建構函式丟擲了異常,那麼已經申請的記憶體誰來回收,答案當然是。因為整個new operator還未結束。所以你無法獲得控制權。如果你對operator new/delete進行了過載,那麼編譯器會呼叫那個operator delete呢?由於不同的operator new可能透過不同的方法獲得記憶體,而讓不知道怎樣分配的operator delete去釋放記憶體顯然是不負責任的。所以編譯器假定哪個operator delete與operator new有相同的引數(除了size_t size和void* pDeAlloc),那麼那個operator delete便知道operator new怎樣獲得記憶體。建構函式丟擲異常後,也會呼叫與operator new相同引數的operator delete來釋放記憶體。”
“那麼,我該怎麼解決現在的問題呢?”
“別那麼急,已經下午五點了。該回家了,別讓人以為程式設計師是夜遊神。回家看看Effective C++第二部分和GotW9, GotW10。明天再說吧。”
第二天一早,Solmyr啃著大餅走進了辦公室,看到Guru Zero早已姿勢優雅地坐在前收發E,不禁自慚形穢,連忙放下手中的大餅,跑去和Zero說:“老大,昨天晚上,我挑燈夜讀,總結出兩點:
1. 如果你寫了一個operator new,請對應寫一個operator delete。
2. 不要delete不是自己new來的記憶體。
“嗯,不錯,怎麼我看上去,就象是Effective C++中的條款呢?你有沒有自己想過關於placement delete的問題?”
“placement delete有什麼問題嗎?”Solmyr一臉茫然的問道。
“你有沒有試過把一塊用placement new申請得到的記憶體用placement delete卸除掉呢?不妨你現在試試看。”
只見Solmyr跑到一臺電腦前,兩手如飛在鍵盤上敲擊,可是我們能聽到的只有他的唉聲嘆氣和編譯器的哇哇大叫。Solmyr實在是沒辦法,只能向Zero求教。Zero喝了一口咖啡,說道:“我們平時寫的那些要求額外引數的operator delete只有在建構函式丟擲異常時,才會被編譯器自動呼叫,而我們是不可能手工呼叫到任何帶有額外引數的delete的,為什麼沒有一個內建的‘placement delete’來與‘placement new’相匹配的原因在於沒有辦法保證它被正確使用,在C++型別系統中,無法推斷一個指標從哪裡獲得它指向的記憶體,可能是指向heap,也可能是stack。所以……”
“所以當我們確實知道一個指標它是怎麼獲得它所指向的記憶體時,我們可以這樣:
template
{
if (p) {
p->~T(); // explicit destructor call
a.deallocate(p);
}
}
這樣,就不會有資源洩漏了。”[2]
“可是我怎麼解決我那稀有的記憶體的問題呢?”
“標準庫中的allocator和boost中的pool可以解決記憶體管理的問題,不必每次呼叫operator new,從而少了一些開銷,不過早上我剛收到客戶的mail,他們說準備在他們的裝置上再加12MB的記憶體。”
“……”Solmyr鬱悶中。
[1] Counting in C++ CUJ 1998.4 中文譯文 陳崴 (程式設計師雜誌:2001.8 雜誌上少了placement new and placement delete)
[2]'s C++ Style and Technique FAQ
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991596/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 一個故事看懂記憶體條工作原理記憶體
- 【轉】java中的記憶體溢位和記憶體洩漏Java記憶體溢位
- linux記憶體管理(一)實體記憶體的組織和記憶體分配Linux記憶體
- 堆記憶體和棧記憶體詳解(轉載)記憶體
- 併發程式背後的故事以及併發當中的記憶體模型記憶體模型
- 一些轉儲和清除記憶體物件和物理物件的命令(轉)記憶體物件
- 記憶體管理之一 (轉)記憶體
- linux 查詢最消耗CPU和記憶體的程式Linux記憶體
- 一些轉儲和清除記憶體物件和物理物件的命令記憶體物件
- Vector() 記憶體釋放 不得不說的故事記憶體
- 為SSD程式設計(3):頁、塊和快閃記憶體轉換層程式設計記憶體
- JVM上的併發和Java記憶體模型之同步塊筆記JVMJava記憶體模型筆記
- struct和malloc記憶體互轉例子Struct記憶體
- 實體記憶體和虛擬記憶體記憶體
- 修改後的 OpenJDK 記憶體模型JDK記憶體模型
- java棧記憶體和堆記憶體的詮釋Java記憶體
- 關於記憶體的一些bug (轉)記憶體
- 一種高效的 C++ 固定記憶體塊分配器C++記憶體
- “瑜珈山夜話” ----記憶體分配(一) (轉)記憶體
- VI 跳到最後一行和跳到最後一行的最後一個字元字元
- 來了解一下記憶體溢位和記憶體洩漏記憶體溢位
- redis的記憶體滿了之後,redis如何回收記憶體嗎Redis記憶體
- 記憶體對映檔案詳解-----C++實現(即一塊記憶體和一個檔案相對映對應)記憶體C++
- 什麼是Java記憶體模型(JMM)中的主記憶體和本地記憶體?Java記憶體模型
- Java記憶體區域和記憶體模型Java記憶體模型
- 記憶體溢位和記憶體洩露記憶體溢位記憶體洩露
- 直接記憶體和堆記憶體誰快記憶體
- 記憶體洩漏和記憶體溢位記憶體溢位
- [轉載] Java直接記憶體與堆記憶體Java記憶體
- NIO的JVM記憶體和機器記憶體的選擇JVM記憶體
- VI操作--跳到最後一行和跳到最後一行的最後一個字元字元
- oracle程式和記憶體轉儲說明Oracle記憶體
- 查詢最佔資源、CPU、記憶體、和執行最長的SQL語句記憶體SQL
- 開源筆記軟體 Joplin 背後的故事筆記
- 【記憶體洩漏和記憶體溢位】JavaScript之深入淺出理解記憶體洩漏和記憶體溢位記憶體溢位JavaScript
- 一種不易察覺的記憶體洩露 (轉)記憶體洩露
- VCL 中的一個記憶體洩漏 Bug (轉)記憶體
- 遊戲記憶體對比普通記憶體區別 遊戲記憶體和普通記憶體相差大嗎?遊戲記憶體