2008-08-11 143 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”,因此永远不会产生负向索引。