""/>

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

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

Of Memory Management, Heap Corruption, and C++

因此,我需要一些帮助。我正在使用C ++进行项目。但是,我认为我已经设法破坏了我的堆。这是基于以下事实:我在类中添加了std::string并从另一个std::string为其分配了一个值:

1
2
3
4
5
std::string hello ="Hello, world.
"
;
/* exampleString ="Hello, world.
" would work fine. */

exampleString = hello;

堆栈转储导致系统崩溃。因此,基本上我需要停下来浏览所有的代码和内存管理内容,并找出我搞砸的地方。代码库仍然很小(大约1000行),因此很容易做到。

尽管如此,我对这种东西仍然不屑一顾,所以我认为我应该把它扔在那里。我在Linux系统上,并使用valgrind进行了搜索,虽然不完全了解我在做什么,但它确实报告了std::string的析构函数是无效的免费程序。我必须承认,从Google搜索中会获得"堆腐败"一词;任何有关此类材料的通用文章也将不胜感激。

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

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


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

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

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

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

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

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

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

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

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

    4)继续直到触发观察点:

    1
    2
    3
    4
    5
    6
    7
    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",因此永远不会导致负索引。


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

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

  • 熟悉调试器。
  • 开始在调试器中四处漫游,看看是否可以找到看起来像腥的东西。特别检查以查看exampleString = hello;行中发生了什么。
  • 检查以确保它实际上在exampleString = hello;行上崩溃,而不是在退出某些封闭块时崩溃(这可能导致析构函数触发)。
  • 检查您可能正在执行的任何指针魔术。指针算术,转换等
  • 检查所有分配和释放,以确保它们匹配(没有重复分配)。
  • 确保您没有返回任何对堆栈上对象的引用或指针。
  • 还有很多其他尝试。我敢肯定,其他人也会对创意产生兴趣。


    一些开始的地方:

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

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


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

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

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

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


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

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

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


    运行净化。

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

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

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

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

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

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


    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.

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


    该代码只是我的程序失败的一个示例(Jim分配在堆栈上)。我实际上并不是在寻找"我做错了什么",而是在寻找"我如何诊断自己做错了什么"。教男人钓鱼和所有这些。尽管看了这个问题,但我还没有说清楚。感谢您的编辑功能。 :')

    另外,我实际上修复了std :: string问题。怎么样?通过将其替换为向量,进行编译,然后再次替换字符串。它一直在那儿崩溃,即使它...也无法解决。那里有些讨厌,我不确定是什么。我确实想检查一下我在堆上手动分配内存的时间:

    1
    2
    3
    4
     this->map = new Area*[largestY + 1];
     for (int i = 0; i < largestY + 1; i++) {
         this->map[i] = new Area[largestX + 1];
     }

    并删除它:

    1
    2
    3
    4
    for (int i = 0; i < largestY + 1; i++) {
        delete [] this->map[i];
    }
    delete [] this->map;

    我以前没有用C ++分配2d数组。它似乎有效。


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

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

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

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


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

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


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

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


    推荐阅读