2008-08-11 182 views
16

所以,我需要一些幫助。我正在使用C++開發一個項目。不過,我想我已經設法破壞了我的堆。這是基於我添加了一個std::string一類的事實,從另一個std::string分配一個值:內存管理,堆損壞和C++

std::string hello = "Hello, world.\n"; 
/* exampleString = "Hello, world.\n" would work fine. */ 
exampleString = hello; 

崩潰我有一個堆棧轉儲系統。所以基本上我需要停止並通過我所有的代碼和內存管理的東西,找出我搞砸了。代碼庫仍然很小(大約1000行),所以這很容易實現。

不過,我對這種東西滿腦子,所以我想我會把它扔在那裏。我在一個Linux系統上,並與valgrind探討,雖然不知道我在做什麼,但它確實報告std::string的析構函數是一個無效的自由。我不得不承認從谷歌搜索中獲得「堆腐敗」這個詞;任何有關這類東西的通用文章也將受到讚賞。

(在rm -rf ProjectDir之前,在C#中再次這樣做:d)

編輯: 我還沒有明確表態,但我要問的是如何診斷這些種類的內存問題的意見。我知道std :: string的東西是正確的,所以這是我所做的事情(或者一個錯誤,但沒有選擇的問題)。我確信我可以檢查我寫的代碼,你很聰明的人很快就會看到這個問題,但我想把這種代碼分析添加到我的'工具箱'中。

回答

21

這些都是比較便宜的機制,有可能解決這個問題:

  1. 請關注我的heap corruption question - 我與更新的答案,因爲他們抖了出來。首先是平衡new[]delete[],但你已經這樣做了。
  2. valgrind更多的去;這是一個很好的工具,我只希望它可以在Windows下使用。我只會減慢大約一半的程序,與Windows相比,這相當不錯。
  3. 考慮使用Google Performance Tools作爲替換malloc/new。
  4. 你清理了所有的目標文件並重新開始了嗎?也許你的make文件是...「次優」
  5. 你不是assert()足夠你的代碼。我怎麼知道,沒有看到它?像牙線一樣,沒有人在他們的代碼中足夠了。爲你的對象添加一個驗證函數,並在方法開始和方法結束時調用它。
  6. 你是compiling -wall?如果沒有,那就這樣做。
  7. 找你自己的皮棉工具,如PC-Lint。像你這樣的小應用可能適用於PC-lint demo頁面,這意味着您無需購買!
  8. 檢查你是刪除指針後NULLing。沒有人喜歡搖晃的指針。與已聲明但未分配的指針相同的演出。
  9. 停止使用數組。改爲使用vector
  10. 不要使用原始指針。使用smart pointer。請勿使用auto_ptr!那件事是令人驚訝的;它的語義非常奇怪。相反,請選擇Boost smart pointers之一,或者從the Loki library中選擇一個。
+2

+1,很好的清單!然而,我反對#8 - 雖然它阻止了「壞」訪問,但它實際上是一種代碼異味,隱藏了我的經驗中不良的邏輯或糟糕的對象生命週期管理...... – Roddy 2010-03-15 15:46:40

+0

現在,C++在標準中有自己的智能指針庫,所以不需要Boost或Loki。 – 2015-12-21 15:41:03

0

據我可以告訴你的代碼是正確的。假設exampleString是一個類似於你所描述的類範圍的std :: string,你應該能夠以這種方式初始化/分配它。也許還有其他一些問題?也許一小段實際的代碼會幫助把它放在上下文中。

問題:exampleString是一個指向用new創建的字符串對象的指針嗎?

1

它可能是堆腐敗,但它可能是堆棧損壞。吉姆是對的。我們確實需要更多的背景。這兩條來源並沒有孤立地告訴我們很多。可能有許多事情會導致這種情況(這是C/C++的真正樂趣)。

如果您願意發佈您的代碼,您甚至可以將其全部放在服務器上併發布鏈接。我相信你會以這種方式得到更多的建議(其中一些毫無疑問與你的問題無關)。

1

我看到的代碼沒有錯誤。如前所述,需要更多的背景。

如果您還沒有嘗試過,請安裝gdb(gcc調試器)並使用-g編譯程序。這將在gdb可以使用的調試符號中進行編譯。一旦你安裝了gdb,使用程序(gdb)運行它。 This是使用gdb的有用cheatsheat。

爲產生錯誤的函數設置一個斷點,並查看exampleString的值是什麼。對於傳遞給exampleString的任何參數也要這樣做。這應該至少告訴你std :: strings是否有效。

我發現this article的答案是一個很好的關於指針的指南。

1

該代碼僅僅是我的程序失敗的一個例子(它被分配到堆棧上,Jim)。我實際上並不是在尋找'我做錯了什麼',而是'我如何診斷我做錯了什麼'。教一個人釣魚等等。雖然看這個問題,但我沒有說清楚。感謝上帝的編輯功能。 :')

此外,我實際上修復了std :: string問題。怎麼樣?通過用向量替換它,編譯,然後再次替換字符串。它一直在那裏崩潰,即使它不能。那裏有些討厭的東西,我不確定是什麼。我要檢查的一個時間我在堆上手動分配內存,但:

this->map = new Area*[largestY + 1]; 
for (int i = 0; i < largestY + 1; i++) { 
    this->map[i] = new Area[largestX + 1]; 
} 

,並刪除它:

for (int i = 0; i < largestY + 1; i++) { 
    delete [] this->map[i]; 
} 
delete [] this->map; 

我以前沒有分配與C++二維數組。它似乎工作。

7

哦,如果你想知道如何調試問題,那很簡單。首先,找一隻死雞。然後,start shaking it

說真的,我還沒有找到一致的方法來跟蹤這些類型的錯誤。因爲存在很多潛在的問題,所以沒有簡單的檢查清單。但是,我會推薦以下內容:

  1. 在調試器中獲得舒適。
  2. 開始在調試器中亂轉,看看你是否能找到任何看起來很腥的東西。請特別檢查以查看exampleString = hello;行中發生的情況。
  3. 檢查以確保它實際上在exampleString = hello;行上崩潰,而不是在退出某個封閉塊(可能導致析構函數觸發)時崩潰。
  4. 檢查你可能正在做的任何指針魔術。指針算術,鑄造等
  5. 檢查所有的分配和釋放,以確保它們匹配(沒有雙重分配)。
  6. 確保您沒有返回任何引用或指向堆棧上的對象的指針。

還有很多其他的事情可以嘗試。我相信其他人也會有想法。

1

此外,我實際上修復了std :: string問題。怎麼樣?通過用向量替換它,編譯,然後再次替換字符串。它一直在那裏崩潰,而且即使它不能。那裏有些討厭的東西,我不確定是什麼。

這聽起來像你真的搖了雞。如果你不知道爲什麼它現在正在工作,那麼它仍然是壞的,幾乎保證在以後再次咬你(在你增加了更多的複雜性之後)。

3

有些地方開始:

如果您使用的是Windows,並使用Visual C++ 6.0(我希望上帝沒有人仍然使用這些天),它的std :: implentation字符串不是線程安全的,並可能導致這種事情。

Here's an article I found which explains a lot of the common causes of memory leaks and corruption.

在我以前的工作,我們使用Compuware公司的BoundsChecker,以幫助這一點。這是商業和非常昂貴的,所以可能不是一種選擇。

這裏有一對夫婦的免費圖書館其中可能會有一些使用

http://www.codeguru.com/cpp/misc/misc/memory/article.php/c3745/

http://www.codeproject.com/KB/cpp/MemLeakDetect.aspx

希望有所幫助。內存腐敗是一個糟糕的地方!

1

運行Purify。

這是一個近乎神奇的工具,當你重挫內存,你不應該接觸,通過不釋放的事情,雙釋放,等內存泄漏,將報告

它可以在機器代碼級別,所以你甚至不必擁有源代碼。我曾經參加過的最令人愉快的供應商電話會議之一是當Purify在他們的代碼中發現內存泄漏時,我們可以問,「是否有可能在你的函數foo()中沒有釋放內存? 「並聽到他們的聲音中的驚訝。

他們認爲我們在調試神,但是我們讓他們知道這個祕密,所以他們可以在我們使用他們的代碼之前運行Purify。 :-)

http://www-306.ibm.com/software/awdtools/purify/unix/

(這是相當昂貴的,但他們有一個自由的eval下載)

1

其中之一,我經常使用(除了最極端的古怪案件)的調試技術是分並征服。如果你的程序目前失敗並出現一些特定的錯誤,那麼以某種方式將它分成兩半,看看它是否仍然有相同的錯誤。很明顯,訣竅就是決定在哪裏劃分你的程序!

您給出的示例沒有顯示足夠的上下文來確定錯誤的可能位置。如果有其他人嘗試你的例子,它會正常工作。所以,在你的程序中,儘量刪除你沒有給我們看到的額外的東西,看看它是否有效。如果是這樣,那麼一次添加其他代碼,直到它開始失敗。然後,你剛剛添加的東西可能是問題。

請注意,如果您的程序是多線程的,那麼您可能有更大的問題。如果沒有,那麼你應該能夠以這種方式縮小它。祝你好運!

1

除了像Boundschecker或Purify這樣的工具之外,解決這類問題的最佳方法就是非常善於閱讀代碼並熟悉您正在編寫的代碼。

內存損壞是解決問題最困難的事情之一,通常這些類型的問題可以通過在調試器中花費幾小時/天來解決,並且注意到「嘿,指針X在被刪除後正在使用中!」。

如果它有幫助,那麼隨着經驗的增加,它會變得更好。

您爲陣列分配的內存看起來是正確的,但請確保您檢查了您訪問陣列的所有位置。

10

我們曾經有過一個缺乏所有常規技術,valgrind,purify等的bug。只有在有大量內存的機器上纔會發生崩潰,而且只發生在大型輸入數據集上。

最終我們使用調試器觀察點來追蹤它。我將嘗試描述這裏的程序:

1)找出失敗的原因。從你的示例代碼看來,「exampleString」的內存被破壞了,所以不能被寫入。讓我們繼續這個假設。

2)在最後已知的位置設置斷點,使用或修改「exampleString」時沒有任何問題。

3)添加一個觀察點到'exampleString'的數據成員。使用我的g ++版本,字符串存儲在_M_dataplus._M_p中。我們想知道這個數據成員何時改變。該GDB技術是這樣的:

(gdb) p &exampleString._M_dataplus._M_p 
$3 = (char **) 0xbfccc2d8 
(gdb) watch *$3 
Hardware watchpoint 1: *$3 

我明明使用的Linux使用g ++和GDB這裏,但我相信,內存觀察點可與大多數調試。

4)繼續,直到觀察點被觸發:

Continuing. 
Hardware watchpoint 2: *$3 

Old value = 0xb7ec2604 "" 
New value = 0x804a014 "" 
0xb7e70a1c in std::string::_M_mutate() from /usr/lib/libstdc++.so.6 
(gdb) where 

GDB的where命令會給出什麼導致了修改後的痕跡。這是一個完全合法的修改,在這種情況下只是繼續 - 或者如果幸運的話,它將是由於內存損壞而導致的修改。在後一種情況下,您現在應該能夠查看真的導致問題的代碼並希望修復它。

我們的錯誤的原因是一個負數索引的數組訪問。該索引是一個指向'int'的指針的模型的結果,該數組的大小取決於數組的大小。 valgrind等人錯過了這個錯誤。因爲在這些工具下運行時分配的內存地址永遠不會是「> MAX_INT」,因此永遠不會產生負向索引。