【问题标题】:Heap corruption堆损坏
【发布时间】:2010-11-15 12:31:25
【问题描述】:

如果我们在 char 数组的 new 和 delete 之间有一大段代码,为什么会出现问题。

Example

void this_is_bad() /* You wouldn't believe how often this kind of code can be found */
{
  char *p = new char[5];    /* spend some cycles in the memory manager */
  /* do some stuff with p */
  delete[] p;      /* spend some more cycles, and create an opportunity for a leak */
}

【问题讨论】:

  • 我不确定您真正要问的是什么。在 "new" 和 "delete" 之间有很多代码并没有错。不管你有多少代码,堆损坏都是不好的。
  • 问题似乎是“给定的代码如何为泄漏创造机会?”
  • 我敢打赌这是一个很快就要到期的家庭作业问题。

标签: c++ heap-memory heap-corruption


【解决方案1】:

我认为在任何变量的新建和删除之间有大量代码不是问题。使用 new 的想法是在堆上放置一个值,从而使其长时间保持活动状态。对这些值执行代码是预期的操作。

如果在 new 和 delete 之间的同一方法中有大量代码,可能会给您带来麻烦,因为...

  • 抛出异常
  • 方法太长以至于看不到开头或结尾,因此人们开始从中间任意返回而没有意识到他们跳过了删除调用

这两个问题都可以通过使用 RAII 类型(例如 std::vector<char>)而不是新 / 删除对来修复。

【讨论】:

  • 通常,我宁愿使用 std::string 而不是 std::vector.
  • @Brian,当将其视为数组类型时,我更喜欢 vector 以保持一致性,但不能反对任何 RAII 类型而不是手动 new/delete。
  • @Brian:必须同意 Jared 的观点。它可能不是字符串。
【解决方案2】:

您引用的文章表明了这一点

 char p[5]; 

在这种情况下同样有效,并且没有泄漏的危险。

一般来说,您可以通过将分配的内存的生命周期非常清晰来避免泄漏,可以看出新建和删除是相关的。

两者之间的大分离更难检查,需要仔细考虑代码中是否有任何方法可以避免删除。

【讨论】:

    【解决方案3】:

    该代码的链接(和源代码)正在感叹该代码中不必要地使用堆。对于恒定且少量的内存,没有理由不在堆栈上分配它。

    Instead:

    void this_is_good()
    {
       /* Avoid allocation of small temporary objects on the heap*/
       char p[5];    /* Use the stack instead */
       /* do some stuff */
    }
    

    但原始代码本身并没有什么问题,只是不够理想。

    【讨论】:

    • 我喜欢这个。然后,如果我将它作为引用传递,我可以在堆栈中使用指向“p”的指针来避免数组重复。
    • 指向堆栈变量的指针给了我惊喜。您必须确保他们不会发现自己进入任何超出函数返回的堆分配对象;一旦你开始将它们作为参数传递,这就会变得很困难。
    【解决方案4】:

    因为有人可能会抛出异常。
    因为有人可能会添加回报。

    如果在 new 和 delete 之间有很多代码,您可能没有发现需要在 throw/return 之前释放内存?

    为什么你的代码中有一个 RAW 指针。
    使用 std::vector。

    【讨论】:

      【解决方案5】:

      不是,假设“巨大的代码”:

      1. 始终运行“删除 [] p;”在调用“p = new char[size];”之前或“抛出异常”;
      2. 始终运行“p = 0;”在调用“delete [] p;”之后

      第一个条件不满足,会导致p的内容泄露。不满足第二个条件可能会导致双重删除。一般来说,最好使用std::vector,以免出现问题。

      【讨论】:

      • 第 2 点。毫无价值。它是 C 的后遗症。对于 C++、范围和智能指针,这是很少需要的。
      • 第 2 点并非毫无价值。尝试运行“delete [] p;”,然后运行“delete [] p;”再次。删除运算符不会将 p 设置为 0。
      • 另外,正如我所说,最好将 std::vector 用于这种事情。 (对于单个对象, boost::shared_ptr 是最好的方法,但 scoped_ptr 和 shared_ptr 不适用于数组。
      • 使用 std::vector 或智能指针,您不必担心任何一点。使用 new[] 和 delete[],您必须同时担心两者。我不明白为什么第 2 点无效,但第 1 点无效。
      【解决方案6】:

      你问这样会不会更好?

      void this_is_great()
      {
         char* p = new char[5];
         delete[] p;
         return;
       }
      

      不是。

      【讨论】:

        【解决方案7】:

        那个特定的例子并不是说在 new 和 delete 之间有一堆代码一定是不好的;它指出,如果有办法编写不使用堆的代码,您可能更喜欢这样做以避免堆损坏。

        这是足够体面的建议;如果减少使用堆的数量,那么当堆损坏时,您就知道在哪里查看。

        【讨论】:

          【解决方案8】:

          除了所有关于堆的有趣答案,以及关于让 new 和 delete 彼此靠近发生的问题之外,我可能会补充一点,要避免在一个函数中包含大量代码的事实。如果大量代码将相关的两行代码分开,那就更糟了。

          我会区分“工作量”和“代码量”:

          void do_stuff( char* const p );
          
          void formerly_huge_function() {
             char* p = new char[5];
             CoInitialize( NULL );
             do_stuff( p );
             CoUninitialize();
             delete[] p;
          }
          

          现在do_stuff 可以做很多事情而不会干扰分配问题。但是其他对称的东西也以这种方式保持在一起。

          这一切都与维护您的代码的人有关。一个月后可能就是你了。

          【讨论】:

          • 除了初始化和终结例程不好,具有低内聚和高耦合。不过,您关于函数长度的观点是正确的。
          • @David:确实,在 C++ 中,使用 RAII 可以更好地解决对称例程。不过,有些语言/库/平台没有。
          猜你喜欢
          • 2011-12-03
          • 2011-08-03
          • 1970-01-01
          • 2016-03-11
          • 2015-06-11
          • 2012-06-17
          • 2011-08-20
          • 2011-07-18
          • 1970-01-01
          相关资源
          最近更新 更多