内存管理,堆腐败和C ++

时间:2008-08-11 04:52:51

标签: c++ memory stack heap

所以,我需要一些帮助。我正在用C ++开发一个项目。但是,我想我已经设法破坏了我的堆。这是基于我向一个类添加std::string并为其分配了另一个std::string的值的事实:

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的内容是正确的,所以这是我做过的事情(或者是一个bug,但是选择不是问题)。我确信我可以查看我编写的代码,你很聪明的人会立刻看到问题,但我想将这种代码分析添加到我的'工具箱'中,就像它一样。

12 个答案:

答案 0 :(得分:21)

这些是可能解决问题的相对便宜的机制:

  1. 密切注意我的heap corruption question - 当他们动摇时,我正在更新答案。第一个是平衡new[]delete[],但你已经在做了。
  2. 再多花一点valgrind;它是一个很好的工具,我只希望它可以在Windows下使用。我只减慢了你的程序减半,这与Windows等价物相比非常好。
  3. 考虑使用Google Performance Tools作为替代malloc / new。
  4. 您是否清理了所有目标文件并重新开始?也许你的make文件是......“次优”
  5. 你的代码中没有assert()。我怎么知道没见过它?就像使用牙线一样,他们的代码中没有assert()就足够了。为对象添加验证函数,并在方法开始和方法结束时调用它。
  6. 你是compiling -wall吗?如果没有,请这样做。
  7. 找到像PC-Lint这样的lint工具。像您这样的小应用可能适合PC-lint demo页面,这意味着您无需购买!
  8. 删除指针后检查是否为NULL。没有人喜欢悬垂的指针。与已声明但未分配的指针相同的演出。
  9. 停止使用数组。请改用vector
  10. 不要使用原始指针。使用smart pointer。不要使用auto_ptr!那件事......令人惊讶;它的语义很奇怪。而是选择Boost smart pointers中的一个,或the Loki library之外的其他内容。

答案 1 :(得分:10)

我们曾经遇到过一个错误,它避开了所有常规技术,valgrind,purify等。崩溃只发生在具有大量内存且仅在大型输入数据集上的机器上。

最终我们使用调试器观察点来追踪它。我将尝试在这里描述这个程序:

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

我显然在这里使用带有g ++和gdb的linux,但我相信大多数调试器都可以使用内存观察点。

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命令将给出一个返回跟踪,显示导致修改的原因。这是一个完全合法的修改,在这种情况下只是继续 - 或者如果你很幸运,它将是由于内存损坏的修改。在后一种情况下,您现在应该能够查看导致问题的真正的代码,并希望能够解决它。

我们的bug的原因是带有负索引的数组访问。索引是一个指向'int'的指针的结果,模数是数组的大小。 valgrind等人错过了这个漏洞。因为在这些工具下运行时分配的内存地址从不“> MAX_INT”,因此从未导致负索引。

答案 2 :(得分:7)

哦,如果您想知道如何调试问题,那很简单。首先,得到一只死鸡。然后,start shaking it

说真的,我还没有找到一种一致的方法来跟踪这些类型的错误。因为存在很多潜在的问题,所以没有一个简单的清单要经过。但是,我建议如下:

  1. 熟悉调试器。
  2. 开始在调试器中进行操作以查看是否可以找到任何看起来很可疑的内容。请特别检查exampleString = hello;行中发生的情况。
  3. 检查以确保它实际上在exampleString = hello;行上崩溃,而不是在退出某个封闭块时(可能导致析构函数触发)。
  4. 检查您可能正在做的任何指针魔法。指针算术,铸造等
  5. 检查所有分配和解除分配以确保它们匹配(没有双重释放)。
  6. 确保您没有返回任何对堆栈中对象的引用或指针。
  7. 还有很多其他事情要尝试。我相信其他人也会提出想法。

答案 3 :(得分:3)

有些地方要开始:

如果您使用的是Windows,并且使用了Visual C ++ 6(我希望上帝现在还没有人使用它),那么std :: string的实现并不是线程安全的,并且可以导致这种情况。 / p>

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

希望有所帮助。内存损坏是一个很糟糕的地方!

答案 4 :(得分:1)

可能是堆损坏,但它可能是堆栈损坏。吉姆是对的。我们真的需要更多的背景。这两行来源并没有孤立地告诉我们。可能有很多事情导致这种情况(这是C / C ++的真正乐趣)。

如果您愿意发布您的代码,您甚至可以将其全部放在服务器上并发布链接。我相信你会得到更多的建议(其中一些无疑与你的问题无关)。

答案 5 :(得分:1)

除了像Boundschecker或Purify这样的工具之外,解决这类问题的最佳方法就是真正擅长阅读代码并熟悉您正在处理的代码。

内存损坏是最难解决的问题之一,通常这些类型的问题可以通过在调试器中花费数小时/天来解决,并注意到“嘿,指针X在被删除后被使用!”。 / p>

如果它有所帮助,那么当你获得经验时,这会让你变得更好。

您对阵列的内存分配看起来是正确的,但请务必检查您访问阵列的所有位置。

答案 6 :(得分:1)

我能看到的代码没有错误。如上所述,需要更多背景。

如果您还没有尝试过,请安装gdb(gcc调试器)并使用-g编译程序。这将编译gdb可以使用的调试符号。一旦安装了gdb,就用程序(gdb)运行它。 This是使用gdb的有用作弊。

为产生错误的函数设置断点,并查看exampleString的值是什么。对于传递给exampleString的任何参数也要这样做。这至少应该告诉你std :: strings是否有效。

我发现this article的答案是关于指针的好指南。

答案 7 :(得分: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 ++分配过2d数组。它似乎有效。

答案 8 :(得分:1)

  

另外,我实际修复了std :: string问题。怎么样?通过用向量替换它,编译,然后再次替换字符串。它一直在那里崩溃,即使它......也不能解决。那里有些令人讨厌的事情,我不确定是什么。

听起来你确实在摇晃鸡肉。如果您不知道为什么它现在正在运行,那么它仍然会被破坏,并且几乎可以保证稍后再次咬你(在你添加了更多的复杂性之后)。

答案 9 :(得分:1)

运行Purify。

这是一个近乎神奇的工具,可以报告当你破坏记忆时你不应该触摸,通过不释放东西,双重释放等来泄露记忆。

它适用于机器代码级别,因此您甚至不必拥有源代码。

我曾经遇到的最令人愉快的供应商电话会议之一是Purify在他们的代码中发现了内存泄漏,我们能够问:“你的函数foo()中是否有可能没有释放内存”并听到他们的声音中的惊讶。

他们认为我们正在调试上帝,但后来我们让他们了解秘密,这样他们就可以在我们使用他们的代码之前运行Purify。 : - )

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

(这是相当昂贵但他们有免费的eval下载)

答案 10 :(得分:1)

我经常使用的一种调试技术(除了最极端的怪异情况)是分而治之。如果您的程序当前失败并出现某些特定错误,则以某种方式将其除以一半并查看它是否仍然具有相同的错误。显然,诀窍是决定在哪里划分你的程序!

您给出的示例没有显示足够的上下文来确定错误的位置。如果有其他人尝试你的例子,它会工作正常。所以,在你的程序中,尝试删除你没有向我们展示的额外内容,看看它是否有效。如果是这样,那么一次添加其他代码,直到它开始失败。然后,你刚刚添加的东西可能是问题。

请注意,如果您的程序是多线程的,那么您可能遇到更大的问题。如果没有,那么你应该能够以这种方式缩小范围。祝你好运!

答案 11 :(得分:0)

据我所知,你的代码是正确的。假设exampleString是一个std :: string,它具有你描述的类范围,你应该能够以这种方式初始化/赋值。也许还有其他一些问题?也许一段实际代码可以帮助将其置于上下文中。

问题:exampleString是指向使用new?

创建的字符串对象的指针