内存管理,堆损坏和C ++

时间:2020-03-05 18:38:53  来源:igfitidea点击:

因此,我需要一些帮助。我正在使用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的析构函数是无效的免费程序。我必须承认,从Google搜索中会获得"堆腐败"一词;任何有关此类材料的通用文章也将不胜感激。

(在rm -rf ProjectDir之前,在C:D中再次执行)

编辑:
我还没有弄清楚,但是我要问的是一种诊断这类内存问题的建议。我知道std :: string的东西是正确的,所以这是我做过的(或者是一个错误,但是Select没有问题)。我敢肯定,我可以检查我编写的代码,非常聪明的人很快就会看到问题,但是我想将这种代码分析添加到我的"工具箱"中。

解决方案

回答

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

问题:exampleString是否为指向使用new创建的字符串对象的指针?

回答

可能是堆损坏,但也有可能是堆栈损坏。吉姆是对的。我们确实需要更多上下文。这两行消息来源并没有告诉我们很多孤立的地方。可能有很多原因导致此(这是C / C ++的真正乐趣)。

如果你舒服就可以发布代码,你甚至可以扔掉所有的它的服务器上,并张贴链接。我相信我们会通过这种方式获得更多建议(其中某些无疑与问题无关)。

回答

如我所见,代码没有错误。如前所述,需要更多的上下文。

如果尚未尝试安装gdb(gcc调试器),然后使用-g编译程序。这将编译gdb可以使用的调试符号。安装gdb后,请使用程序(gdb)运行它。这是使用gdb的有用技巧。

为产生错误的函数设置一个断点,并查看exampleString的值是什么。对于传递给exampleString的任何参数,也请执行相同的操作。这至少应该告诉我们std :: strings是否有效。

我发现本文的答案是有关指针的很好指南。

回答

该代码只是我的程序失败的一个示例(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数组。它似乎有效。

回答

哦,如果我们想知道如何调试问题,那很简单。首先,得到一只死鸡。然后,开始摇晃它。

严重的是,我还没有找到一种一致的方式来跟踪这些错误。由于存在许多潜在的问题,因此没有简单的清单可以通过。但是,我建议以下内容:

  • 熟悉调试器。
  • 开始在调试器中四处搜索,看看是否可以找到任何看起来令人讨厌的东西。特别检查一下在exampleString = hello;行中发生了什么。
  • 检查并确保它确实在exampleString = hello;行上崩溃,而不是在退出某些封闭块时崩溃(这可能导致析构函数触发)。
  • 检查我们可能正在执行的任何指针魔术。指针算术,转换等
  • 检查所有分配和释放,以确保它们匹配(没有重复分配)。
  • 确保我们没有返回任何对堆栈上对象的引用或者指针。

还有很多其他尝试。我敢肯定,其他人也会对创意产生兴趣。

回答

Also, I actually fixed the std::string problem. How? By replacing it with a vector, compiling, then replacing the string again. It was consistently crashing there, and that fixed even though it...couldn't. There's something nasty there, and I'm not sure what.

听起来我们确实确实在动摇它。如果我们不知道为什么现在可以使用它,那么它仍然会损坏,并且可以保证稍后再咬我们(在我们添加了更多复杂性之后)。

回答

一些开始的地方:

如果我们在Windows上,并且使用可视C ++ 6(我希望天哪,现在还没有人使用它),则std :: string的实现不是线程安全的,并且可能导致这种情况。

我发现这是一篇文章,解释了很多导致内存泄漏和损坏的常见原因。

在我以前的工作场所中,我们使用了Compuware Boundschecker来帮助解决此问题。这是商业性的并且非常昂贵,因此可能不是一个选择。

这是几个免费的库,可能有用

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

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

希望能有所帮助。内存损坏是一个令人毛骨悚然的地方!

回答

运行Purify。

这是一种近乎神奇的工具,当我们正在破坏不应该触摸的内存,不释放东西,重复释放等导致内存泄漏时,它将报告。

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

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

他们以为我们在调试神灵,但随后我们让他们秘密进入,以便他们可以在必须使用其代码之前运行Purify。 :-)

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

(价格很贵,但可以免费下载评估版)

回答

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

给出的示例没有显示足够的上下文来确定错误可能在哪里。如果有人尝试示例,它会很好地工作。因此,在程序中,尝试删除我们没有显示给我们的所有多余内容,然后查看它是否有效。如果是这样,则一次又一次添加其他代码,直到开始失败。然后,我们刚刚添加的内容可能就是问题所在。

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

回答

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

  • 请密切注意我的堆损坏问题-当他们摆脱出来时,我会在提供最新答案。首先是平衡new []delete [],但是我们已经在这样做了。
  • 多给valgrind一试;这是一个出色的工具,我只希望它在Windows下可用。我只会将程序减慢大约一半的速度,与Windows等效程序相比,这是相当不错的。
  • 考虑使用Google Performance Tools替代malloc / new。
  • 我们是否清理了所有目标文件并重新开始?也许make文件是..."次优"
  • 我们在代码中没有足够的" assert()"功能。我怎么不看就知道呢?像使用牙线一样,在他们的代码中没有一个assert()就足够了。为对象添加一个验证函数,并在方法开始和方法结束时调用它。
  • 我们正在编译-wall吗?如果没有,请这样做。
  • 找到适合自己的皮棉工具,例如PC-Lint。像PC这样的小应用程序可能会适合于PC-lint演示页面,这意味着我们无需购买!
  • 删除指针后检查是否正在清空指针。没有人喜欢悬空的指针。与已声明但未分配的指针相同的演出。
  • 停止使用数组。请改用向量。
  • 不要使用原始指针。使用智能指针。不要使用auto_ptr!那东西真是令人惊讶。它的语义很奇怪。相反,请选择Boost智能指针之一或者从Loki库中选择的东西。

回答

除了诸如Boundschecker或者Purify之类的工具外,解决此类问题的最佳选择就是真正善于阅读代码并熟悉正在使用的代码。

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

如果有帮助,那么随着经验的积累,我们会变得更好。

我们为该阵列分配的内存看起来正确,但请确保也检查所有访问该阵列的位置。

回答

我们曾经有一个错误,无法正常运行valgrind,purify等所有常规技术。崩溃仅发生在具有大量内存的机器上,并且仅在大型输入数据集上发生。

最终,我们使用调试器监视点对其进行了跟踪。我将在这里描述该过程:

1)查找失败原因。从示例代码中可以看出," exampleString"的内存已损坏,因此无法写入。让我们继续这个假设。

2)在最后一个已知的位置上设置断点,该位置将使用或者修改" exampleString"而没有任何问题。

3)将监视点添加到" exampleString"的数据成员。在我的g ++版本中,字符串存储在_M_dataplus._M_p中。我们想知道该数据成员何时更改。 GDB的技术是:

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

我显然在这里将Linux与g ++和gdb一起使用,但是我相信大多数调试器都可以使用内存监视点。

4)继续直到触发监视点:

Continuing.
Hardware watchpoint 2: *

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

gdbwhere命令将回溯显示修改的结果。这是完全合法的修改,在这种情况下,只需继续操作即可;否则,由于内存损坏,这将是一次修改。在后一种情况下,我们现在应该可以查看真正引起问题的代码,并希望予以解决。

我们的bug的原因是带有负索引的数组访问。索引是将指针强制转换为" int"的结果,对数组的大小进行模运算。该错误被valgrind等人遗漏了。因为在这些工具下运行时分配的内存地址永远不会是"> MAX_INT",因此永远不会导致负索引。