C 中的記憶體洩露

2021-04-23 15:50:01 字數 2728 閱讀 3468

據說愛因斯坦曾提出過這樣的建議:盡可能地讓事情簡單,但不要過於簡單。在c++語言中相似的說法應該是:盡可能地使程式高效,但不要過於高效。

一旦程式設計師抓住了「傳值」在效率上的把柄(參見條款22),他們會變得十分極端,恨不得挖出每乙個隱藏在程式中的傳值操作。豈不知,在他們不懈地追求純粹的「傳引用」的過程中,他們會不可避免地犯另乙個嚴重的錯誤:傳遞乙個並不存在的物件的引用。這就不是好事了。

看乙個表示有理數的類,其中包含乙個友元函式,用於兩個有理數相乘:

class rational ;

inline const rational operator*(const rational& lhs,

const rational& rhs)

很明顯,這個版本的operator*是通過傳值返回物件結果,如果不去考慮物件構造和析構時的開銷,你就是在逃避作為乙個程式設計師的責任。另外一件很明顯的事實是,除非確實有必要,否則誰都不願意承擔這樣乙個臨時物件的開銷。那麼,問題就歸結於:確實有必要嗎?

答案是,如果能返回乙個引用,當然就沒有必要。但請記住,引用只是乙個名字,乙個其它某個已經存在的物件的名字。無論何時看到乙個引用的宣告,就要立即問自己:它的另乙個名字是什麼呢?因為它必然還有另外乙個什麼名字(見條款m1)。拿operator*來說,如果函式要返回乙個引用,那它返回的必須是其它某個已經存在的rational物件的引用,這個物件包含了兩個物件相乘的結果。

但,期望在呼叫operator*之前有這樣乙個物件存在是沒道理的。也就是說,如果有下面的**:

rational a(1, 2);                // a = 1/2

rational b(3, 5);                // b = 3/5

rational c = a * b;              // c 為 3/10

期望已經存在乙個值為3/10的有理數是不現實的。如果operator* 一定要返回這樣乙個數的引用,就必須自己建立這個數的物件。

乙個函式只能有兩種方法建立乙個新物件:在堆疊裡或在堆上。在堆疊裡建立物件時伴隨著乙個區域性變數的定義,採用這種方法,就要這樣寫operator*:

// 寫此函式的第乙個錯誤方法

inline const rational& operator*(const rational& lhs,

const rational& rhs)

這個方法應該被否決,因為我們的目標是避免建構函式被呼叫,但result必須要象其它物件一樣被構造。另外,這個函式還有另外乙個更嚴重的問題,它返回的是乙個區域性物件的引用,關於這個錯誤,條款31進行了深入的討論。

那麼,在堆上建立乙個物件然後返回它的引用呢?基於堆的物件是通過使用new產生的,所以應該這樣寫operator*:

// 寫此函式的第二個錯誤方法

inline const rational& operator*(const rational& lhs,

const rational& rhs)

首先,你還是得負擔構造函式呼叫的開銷,因為new分配的記憶體是通過呼叫乙個適當的建構函式來初始化的(見條款5和m8)。另外,還有乙個問題:誰將負責用delete來刪除掉new生成的物件呢?

實際上,這絕對是乙個記憶體洩漏。即使可以說服operator*的呼叫者去取函式返回值位址,然後用delete去刪除它(絕對不可能——條款31展示了這樣的**會是什麼樣的),但一些複雜的表示式會產生沒有名字的臨時值,程式設計師是不可能得到的。例如:

rational w, x, y, z;

w = x * y * z;

兩個對operator*的呼叫都產生了沒有名字的臨時值,程式設計師無法看到,因而無法刪除。(再次參見條款31)

也許,你會想你比一般的熊——或一般的程式設計師——要聰明;也許,你注意到在堆疊和堆上建立物件的方法避免不了對建構函式的呼叫;也許,你想起了我們最初的目標是為了避免這種對建構函式的呼叫;也許,你有個辦法可以只用乙個建構函式來搞掂一切;也許,你的眼前出現了這樣一段**:operator*返回乙個「在函式內部定義的靜態rational物件」的引用:

// 寫此函式的第三個錯誤方法

inline const rational& operator*(const rational& lhs,

const rational& rhs)

這個方法看起來好象有戲,雖然在實際實現上面的偽**時你會發現,不呼叫乙個rational建構函式是不可能給出result的正確值的,而避免這樣的呼叫正是我們要談論的主題。就算你實現了上面的偽**,但,你再聰明也不能最終挽救這個不幸的設計。

想知道為什麼,看看下面這段寫得很合理的使用者**:

bool operator==(const rational& lhs,      // rationals的operator==

const rational& rhs);     //

rational a, b, c, d;

if ((a * b) == (c * d)) else

的確,這會導致「operator*的返回值構造和析構時帶來的開銷」,但歸根結底它只是用小的代價換來正確的程式執行行為而已。況且,你所擔心的開銷還有可能永遠不會出現:和所有程式語言一樣,c++允許編譯器的設計者採用一些優化措施來提高所生成的**的效能,所以,在有些場合,operator*的返回值會被安全地除去(見條款m20)。當編譯器採用了這種優化時(當前大部分編譯器這麼做),程式和以前一樣繼續工作,只不過是執行速度比你預計的要快而已。

以上討論可以歸結為:當需要在返回引用和返回物件間做決定時,你的職責是選擇可以完成正確功能的那個。至於怎麼讓這個選擇所產生的代價盡可能的小,那是編譯器的生產商去想的事。

C 記憶體洩露

簡單點說就是只申請不釋放 在c 中我們通過malloc,new來通過系統申請記憶體,但當我們使用完畢之後 裡面可能就沒有執行相應的free和delete的操作,這樣我們申請的這部分記憶體系統就不會再次的 分配,這就造成了記憶體洩露!這篇帖子講的很細大家可以研究研究。我們在delete乙個指標之後要將...

C 記憶體洩露

boost基礎 any int p new int 10 應該用 shared ptrp new int 10 boost any a p 危險,會造成記憶體洩露any 的析構函式會刪除內部的 holder 物件 any 是包裝類 如果型別是指標,any 並不會對指標執行 delete 操作,因此,...

C 記憶體洩露

c 記憶體洩露主要發生在淺拷貝階段 例如 include include include includeusing namespace std class a a int main 由於發生了淺拷貝,所以程式退出的時候,就中斷了,因為同一記憶體析夠了2次。至於為什麼同一物件析夠2次就會中斷,是因為已...