所以,我需要一些帮助。我正在开发一个 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的析构函数是无效的 free。我不得不承认“堆损坏”这个词是从 Google 搜索中得到的。任何有关此类内容的通用文章也将不胜感激。

(在之前 rm -rf ProjectDir, ,在 C# 中再做一次:D)

编辑:我还没有说清楚,但我要求的是诊断此类记忆问题的方法建议。我知道 std::string 的东西是正确的,所以这是我所做的事情(或者是一个错误,但选择没有问题)。我确信我可以检查我编写的代码,你们这些非常聪明的人很快就会发现问题,但我想将这种代码分析添加到我的“工具箱”中。

有帮助吗?

解决方案

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

  1. 关注我的 堆损坏问题 - 我正在更新答案。第一个是平衡 new[]delete[], ,但你已经这样做了。
  2. 瓦尔格林德 更多的是一次尝试;它是一个出色的工具,我只希望它可以在 Windows 下使用。我只将你的程序速度减慢了大约一半,这与 Windows 的同等程序相比已经相当不错了。
  3. 考虑使用 谷歌性能工具 作为 malloc/new 的替代品。
  4. 您是否已清除所有目标文件并重新开始?也许你的 make 文件是...“次优”
  5. 你不 assert()在你的代码中已经足够了。没有亲眼所见我怎么知道呢?就像使用牙线一样,没有人 assert()他们的代码就足够了。为您的对象添加验证函数,并在方法开始和方法结束时调用该函数。
  6. 你是 编译墙?如果没有,就这样做。
  7. 为自己找到一个 lint 工具,例如 PC-Lint. 。像您这样的小应用程序可能适合 PC-lint 演示 页面,意味着您没有购买!
  8. 检查删除指针后是否将其置空。没有人喜欢悬空指针。具有已声明但未分配的指针的相同演出。
  9. 停止使用数组。用一个 向量 反而。
  10. 不要使用原始指针。用一个 智能指针. 。不要使用 auto_ptr!那东西是...奇怪;它的语义非常奇怪。相反,选择其中之一 增强智能指针, ,或者一些东西 洛基图书馆.

其他提示

我们曾经遇到过一个错误,它无法使用所有常规技术,如 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

全局数据库 where 命令将给出一个回溯,显示修改的结果。这要么是完全合法的修改,在这种情况下只需继续 - 或者如果你幸运的话,这将是由于内存损坏而进行的修改。在后一种情况下,您现在应该能够查看以下代码 真的 造成问题并希望解决它。

我们的错误的原因是使用负索引的数组访问。该索引是将指针转换为以数组大小为模的“int”的结果。valgrind 等人错过了这个错误。因为在这些工具下运行时分配的内存地址从来都不是“> MAX_INT”,因此从未导致负指数。

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

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

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

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

一些可以开始的地方:

如果您在 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

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

这可能是堆损坏,但也可能是堆栈损坏。吉姆是对的。我们确实需要更多背景信息。这两行来源并没有单独告诉我们太多信息。可能有很多因素会导致这种情况(这是 C/C++ 的真正乐趣)。

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

该代码只是我的程序失败的一个示例(它是在堆栈上分配的,吉姆)。我实际上并不是在寻找“我做错了什么”,而是“我如何诊断我做错了什么”。教人钓鱼等等。虽然看了这个问题,但我还没有说得足够清楚。感谢上帝的编辑功能。:')

另外,我实际上修复了 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++ 分配过二维数组。似乎有效。

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

听起来你确实摇动了一只鸡。如果你不知道 为什么 它现在正在工作,然后它仍然是坏的,并且几乎肯定会在稍后再次咬你(在你添加了更多的复杂性之后)。

运行净化。

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

它在机器代码级别工作,因此您甚至不需要源代码。

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

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

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

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

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

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

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

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

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

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

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

我看到你的代码没有错误。正如已经说过的,需要更多的背景信息。

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

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

我找到了答案 本文 成为关于指针的良好指南。

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top