【问题标题】:deleting an array the wrong way [duplicate]以错误的方式删除数组[重复]
【发布时间】:2011-05-03 22:41:23
【问题描述】:

可能重复:
How could pairing new[] with delete possibly lead to memory leak only?

总是有人告诉我,在使用 new[] 分配的数组上调用 delete 是不安全的。您应该始终将 new 与 delete 以及 new[] 与 delete[] 配对。

所以我惊讶地发现下面的代码在VS2008下的Debug和Release模式下都编译运行正常。

class CBlah
{
public:
    CBlah() : m_i(0) {}

private:
    int m_i;
};

int _tmain(int argc, _TCHAR* argv[])
{
    for(;;)
    {
        CBlah * p = new CBlah[1000]; // with []
        delete p;                    // no []
    }
    return 0;
}

我花了一段时间才弄清楚为什么这会起作用,我认为这只是运气和一些未定义的行为。

但是...这让我想知道...为什么 Visual Studio 不选择它,至少在调试内存管理器中?是因为有很多代码会犯这个错误而他们不想破坏它,还是他们觉得捕捉这种错误不是 Debug 内存管理器的工作?

有什么想法吗?这种滥用常见吗?

【问题讨论】:

  • 这是一个逻辑错误,编译器无法检测到。不过,好的 IDE 可以发现这些。
  • 这个问题stackoverflow.com/questions/1913343/… 彻底解释了所有细节。是的,这是一个典型的错误。
  • @Hans Passant:不,数组没有泄露,这是UB。我链接到一个问题,该问题详细解释了所有内部工作原理。
  • 使用std::vector,不要手动删除东西。

标签: c++ memory-management


【解决方案1】:

它肯定会编译好的,因为指针(编译时)中没有信息可以查看指针是否指向数组或什么。例如:

int* p;

cin>>x;
if(x == 0)
  p = new int;
else
  p = new int [10];

delete p; //correct or not? :)

现在,关于运行正常。这在 C++ 中称为未定义行为,也就是说,无法保证会发生什么 - 一切都可以正常运行,您可能会遇到段错误,您可能会遇到错误的行为,或者您的计算机可能决定拨打 911。UB 不保证

【讨论】:

  • 是的。在实践中,它要么起作用,要么破坏堆。而且堆损坏非常糟糕
  • 也不保证会编译。基本上是UB。没有规则。
  • @Chubsdad 这个编译是有保证的(至少实际上是这样),不是吗?因为它永远无法知道指针是否指向数组或什么。一般来说,确实,UB涉及编译失败,它实际上涉及所有内容。
  • 堆损坏非常糟糕的原因是,当它崩溃或以其他方式崩溃时,它会在某个点远离错误在哪里......如果你不快速掌握,你将花费数小时或数天尝试调试可能非常好的代码,而没有意识到真正的错误可能在完全不同的部分程序。
  • 谢谢大家...但我仍然不明白为什么内存管理器无法在运行时检测到这一点,至少在调试中。它在每个分配的块前面加上一个 32 字节的标头(据我所知)。这包括数组中的项目数(因此 delete[] 知道调用析构函数的次数)......无论如何......它是 UB,我同意。
【解决方案2】:

这是未定义的行为,在爱情、战争和未定义的行为中一切都是公平的......:)

【讨论】:

    【解决方案3】:

    根据 MDSN,它在尝试删除数组时将 delete 转换为 delete[]。 (例如,参见there)。虽然编译后你应该有一个警告。

    【讨论】:

      【解决方案4】:

      Debug Memory Manager 没有发现这个错误的原因可能是因为它不是在 new/delete 级别实现的,而是在 调用的内存管理器级别实现的新建/删除以分配所需的内存。
      到那时,数组 new 和标量 new 之间的区别就消失了。

      【讨论】:

      • 实际上在 Visual C++ 中new[] 总是调用operator new[]()delete 总是调用operator delete(),但它们的实现方式相同——通过malloc()。没有人关心,这是有原因的 - 捕获 C++ 程序中所有可能的错误不是调试 CRT 任务。
      【解决方案5】:

      您可以阅读这些关于deletedelete[] 的SO 答案和链接:About delete, operator delete, delete[], ...

      【讨论】:

        【解决方案6】:

        我不知道是什么让你认为它“工作正常”。它编译并完成而不会崩溃。这并不意味着一定没有泄漏或堆损坏。此外,如果您这次侥幸逃脱,也不一定安全。

        有时,即使是缓冲区覆盖,您也会“逃脱”,因为您写入的字节未被使用(也许它们正在填充对齐)。不过你不应该到处做。

        顺便说一句,new T[1] 是 new[] 的一种形式,即使在这种情况下只有一个元素,它仍然需要 delete[]。

        【讨论】:

          【解决方案7】:

          有趣的点。 有一次我进行了代码审查并试图说服程序员修复 new[]-delete 不匹配。 我曾与 Scott Meyers 的 Effective C++ 中的“Item 5”争论过。然而,他们争辩说“你想要什么,效果很好!”并证明,没有内存泄漏。 但是,它仅适用于 POD 类型。看起来,MS 试图修复 Raveline 指出的不匹配。

          如果添加析构函数会发生什么?

          #include <iostream>
          class CBlah
          {
            static int instCnt;
          public:
              CBlah() : m_i(0) {++instCnt;}
              ~CBlah()
              {
                std::cout <<"d-tor on "<< instCnt <<" instance."<<std::endl;
                --instCnt;
              }
          private:
              int m_i;
          };
          
          int CBlah::instCnt=0;
          
          int main()
          {
              //for(;;)
              {
                  CBlah * p = new CBlah[10]; // with []
                  delete p;                    // no []
              }
              return 0;
          }
          

          无论向 VS 添加什么愚蠢的“智能”修复,代码都不可移植。

          【讨论】:

            【解决方案8】:

            请记住,“正常工作”属于“未定义行为”的范畴。特定编译器的特定版本很可能以适用于所有意图和目的的方式实现这一点。要记住的重要一点是,不能保证,您无法确定它是否 100% 正常工作,也无法知道它是否可以与下一版本的编译器一起工作。它也不是可移植的,因为另一个编译器可能以不同的方式工作。

            【讨论】:

              【解决方案9】:

              这是因为它所链接的特定 C++ 运行时库对operator newoperator new[] 使用相同的堆。许多人这样做,但有些人不这样做,这就是不推荐这种做法的原因。

              另一个很大的区别是,如果CBlah 有一个非平凡的析构函数,delete p; 只会为数组中的第一个对象调用它,而delete[] p; 肯定会为所有对象调用它。

              【讨论】:

              • 这是一个 UB - 任何事情都可能发生。
              • OP 说某些特定代码“运行正常”[或似乎]。即使存在一些尚未导致崩溃的堆损坏,但 delete p 版本只会调用已删除类型的任何析构函数仍然是正确的。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2020-04-12
              • 1970-01-01
              • 1970-01-01
              • 2015-05-25
              • 2015-03-09
              • 2019-01-19
              • 1970-01-01
              相关资源
              最近更新 更多