20

所以,我需要一些帮助。我正在使用 C++ 开发一个项目。但是,我想我已经设法破坏了我的堆。这是基于我将 an 添加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 的东西是正确的,所以这是我做过的事情(或者是一个错误,但 Select 没有问题)。我确信我可以检查我编写的代码,你们非常聪明的人很快就会发现问题,但我想将这种代码分析添加到我的“工具箱”中,就像它一样。

4

12 回答 12

23

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

  1. 密切关注我的堆损坏问题- 我正在更新答案,因为他们已经摆脱了。第一个是平衡new[]delete[],但你已经这样做了。
  2. valgrind更多的尝试;这是一个很好的工具,我只希望它在 Windows 下可用。我只将您的程序减慢了大约一半,这与 Windows 等效程序相比已经相当不错了。
  3. 考虑使用Google 性能工具作为 malloc/new 的替代品。
  4. 您是否已清除所有目标文件并重新开始?也许您的制作文件是......“次优”
  5. assert()的代码还不够。没看过怎么知道?就像使用牙线一样,assert()他们的代码中没有人足够。为您的对象添加一个验证函数,并在方法开始和方法结束时调用它。
  6. 你在编译 -wall吗?如果没有,请这样做。
  7. 给自己找一个 lint 工具,比如PC-Lint。像您这样的小应用程序可能适合PC-lint 演示页面,这意味着您无需购买!
  8. 检查您在删除指针后是否将指针清空。没有人喜欢悬空指针。与已声明但未分配的指针相同的演出。
  9. 停止使用数组。请改用向量
  10. 不要使用原始指针。使用智能指针。不要使用auto_ptr!那东西……令人惊讶;它的语义很奇怪。取而代之的是,选择Boost 智能指针之一,或Loki 库中的某个东西。
于 2008-08-11T11:59:25.777 回答
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

gdbwhere命令将给出一个回溯,显示导致修改的原因。这是一个完全合法的修改,在这种情况下继续 - 或者如果你很幸运,这将是由于内存损坏而导致的修改。在后一种情况下,您现在应该能够查看真正导致问题的代码并希望修复它。

The cause of our bug was an array access with a negative index. The index was the result of a cast of a pointer to an 'int' modulos the size of the array. The bug was missed by valgrind et al. as the memory addresses allocated when running under those tools was never "> MAX_INT" and so never resulted in a negative index.

于 2008-09-16T13:06:18.773 回答
7

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

说真的,我还没有找到一种一致的方法来追踪这些类型的错误。因为有很多潜在的问题,所以没有一个简单的清单可以通过。但是,我会推荐以下内容:

  1. 熟悉调试器。
  2. 开始在调试器中四处游荡,看看是否能找到任何看起来可疑的东西。尤其要检查在线期间发生的情况exampleString = hello;
  3. 检查以确保它确实exampleString = hello;在线崩溃,而不是在退出某些封闭块时(这可能导致析构函数触发)。
  4. 检查您可能正在做的任何指针魔术。指针算术、强制转换等。
  5. 检查所有分配和解除分配以确保它们匹配(没有双重解除分配)。
  6. 确保您没有返回任何指向堆栈上对象的引用或指针。

还有很多其他的东西可以尝试。我相信其他一些人也会提出想法。

于 2008-08-11T06:20:21.810 回答
3

一些开始的地方:

如果您在 Windows 上,并且使用 Visual 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

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

于 2008-08-11T06:49:15.777 回答
1

这可能是堆损坏,但也可能是堆栈损坏。吉姆是对的。我们真的需要更多的上下文。这两行来源并不能单独告诉我们太多。可能有很多原因导致这种情况(这是 C/C++ 的真正乐趣)。

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

于 2008-08-11T05:31:27.213 回答
1

如我所见,您的代码没有错误。正如已经说过的,需要更多的上下文。

如果您还没有尝试过,请安装 gdb(gcc 调试器)并使用 -g 编译程序。这将编译 gdb 可以使用的调试符号。安装 gdb 后,使用程序 (gdb <your_program>) 运行它。是使用 gdb 的有用备忘。

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

我发现这篇文章的答案是关于指针的一个很好的指南。

于 2008-08-11T05:38:47.090 回答
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++ 分配过二维数组。它似乎工作。

于 2008-08-11T06:01:25.610 回答
1

另外,我实际上修复了 std::string 问题。如何?通过用向量替换它,编译,然后再次替换字符串。它一直在那里崩溃,即使它……也不能修复。那里有一些讨厌的东西,我不确定是什么。

听起来你真的对它动摇了。如果您不知道它为什么现在可以工作,那么它仍然是坏的,并且几乎可以保证以后会再次咬您(在您增加了更多复杂性之后)。

于 2008-08-11T06:26:06.853 回答
1

运行净化。

这是一个近乎神奇的工具,当你破坏你不应该接触的内存时,它会报告,不释放东西而泄漏内存,双重释放等等。

它在机器代码级别工作,因此您甚至不必拥有源代码。

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

他们认为我们是在调试神,但后来我们让他们知道了秘密,这样他们就可以在我们不得不使用他们的代码之前运行 Purify。:-)

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

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

于 2008-08-11T07:24:10.137 回答
1

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

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

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

于 2008-08-11T08:34:56.407 回答
1

除了 Boundschecker 或 Purify 之类的工具之外,解决此类问题的最佳选择是真正擅长阅读代码并熟悉您正在处理的代码。

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

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

您为数组分配的内存看起来是正确的,但请确保您也检查了访问数组的所有位置。

于 2008-08-21T19:18:44.040 回答
0

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

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

于 2008-08-11T05:18:52.683 回答