【问题标题】:Is it safe to use delete[] on an array that was allocated as a different type?在分配为不同类型的数组上使用 delete[] 是否安全?
【发布时间】:2015-06-18 01:03:01
【问题描述】:

为了使用 placement new 而不是自动尝试调用默认构造函数,我使用 reinterpret_cast<Object*>(new char[num_elements * sizeof(Object)]) 而不是 new Object[num_elements] 分配一个数组。

但是,我不确定应该如何删除数组以便正确调用析构函数。我是否应该循环遍历元素,为每个元素手动调用析构函数,然后将数组转换为 char* 并在其上使用 delete[],如下所示:

for (size_t i = 0; i < num_elements; ++i) {
     array[i].~Object();
}
delete[] reinterpret_cast<char*>(array);

或者如果我不为每个元素手动调用析构函数就足够了,而仅仅依靠 delete[] 来做到这一点,因为数组的类型是Object*,比如@ 987654329@?

我担心的是,并非每个平台都能以这种方式正确确定数组中的元素数量,因为我没有使用合适的大小。 An answer 对关于“delete[] 如何知道操作数的大小”的问题表示delete[] 的可能实现是存储已分配元素的数量(而不是字节数量)。

如果delete[] 确实以这种方式实现,则表明仅使用delete[] array 会尝试删除太多元素,因为创建数组时使用的char 元素多于适合的Object 元素数量它。所以在这种情况下,删除数组的唯一可靠方法是手动调用析构函数,将数组转换为char*,然后使用delete[]

但是,实现它的另一种合乎逻辑的方法是以字节为单位存储数组的大小,而不是元素的数量,然后在调用delete[] 时,将数组的大小除以数组通过类型的大小来获取要调用析构函数的元素的数量。如果使用此方法,则只需使用 delete[] array 即可,其中 array 的类型为 Object*

所以我的问题是:如果数组最初没有分配正确的类型,我可以依靠 delete[] 正确调用操作数数组中元素的析构函数吗?


这是我正在使用的代码:

template <typename NumberType>
NeuronLayer<NumberType>::NeuronLayer(size_t num_inputs, size_t num_neurons, const NumberType *weights)
    : neurons(reinterpret_cast<Neuron<NumberType>*>(new char[num_neurons * sizeof(Neuron<NumberType>)])),
      num_neurons(num_neurons), num_weights(0) {
    for (size_t i = 0; i < num_neurons; ++i) {
        Neuron<NumberType> &neuron = neurons[i];
        new(&neuron) Neuron<NumberType>(num_inputs, weights + num_weights);
        num_weights += neuron.GetNumWeights();
    }
}

template <typename NumberType>
NeuronLayer<NumberType>::~NeuronLayer() {
    delete[] neurons;
}

template <typename NumberType>
NeuronLayer<NumberType>::~NeuronLayer() {
    for (size_t i = 0; i < num_neurons; ++i) {
        neurons[i].~Neuron();
    }
    delete[] reinterpret_cast<char*>(neurons);
}

【问题讨论】:

  • 数组没有析构函数,也没有“数组的放置删除”。您必须记住分配大小并手动调用所有数组元素析构函数。也就是说,这种情况不会真正出现在可移植代码中,因为首先是placement array new is unusable
  • 您的第一个代码块是释放内存的唯一符合标准的方法。
  • @Kerrek SB 你似乎误解了我的问题。我不是在谈论新的放置数组或数组的放置删除。我只使用placement new 来调用类“Neuron”的构造函数来填充数组。我的问题不是关于任何类型的“放置删除”,而是关于如果内存填充了该类型的正确实例,delete[] 是否会正确调用操作数中元素的操作数指针类型的析构函数,即使它不是作为该类型的数组创建的。
  • @RPFeltz:除非delete[] 的操作数的值是先前的array-new 表达式的结果,否则行为是未定义的。 reinterpret_cast&lt;char*&gt;(array) 显然不是先前的 array-new 表达式的结果(而是 reinterpret-cast 表达式的结果)。
  • @Kerrek SB 你忽略了array 本身是使用new char[num_elements * sizeof(Object)] 创建的,所以它实际上是先前array-new 表达式的结果。演员表在那里,因为在创建之后,我将数组转换为Object*,然后再将其存储在字段中。我的问题本质上是不做delete[] reinterpret_cast&lt;char*&gt;(array)的正确行为有多广泛,而在这种情况下只是delete[] array,考虑到array然后具有不同的类型(Object)而不是它创建的( char).

标签: c++ arrays destructor delete-operator placement-new


【解决方案1】:

Object* 上调用delete[] 将为new[] 分配的每个对象调用一次析构函数。 new Object[N] 通常将 N 存储在实际数组之前,delete[] 当然知道在哪里查找。

您的代码不存储该计数。它不能,因为它是一个未指定的实现细节,存储计数的位置和方式。正如您推测的那样,有两种明显的方法:元素计数和数组大小,以及一个明显的位置(数组之前)。即便如此,也可能存在对齐问题,并且您无法预测大小使用的类型。

另外,new unsigned char[N] 是一种特殊情况,因为delete[] 不需要调用char 的析构函数。在这种情况下,new[] 根本不需要存储 N。因此,即使new Object[N] 会存储一个大小,您甚至不能指望存储该大小。

【讨论】:

  • 致未来的读者:做这种事情时要非常非常小心!如本答案所述,存在对齐问题。在您可能遇到的任何平台上,char 将是 1 字节,因此会有 1 字节对齐要求(即什么都没有)。但是,Object 几乎肯定会有更强的对齐要求;违反此规定是未定义的行为,可能会陷入某些架构,例如 SPARC。
  • 你是对的;谢谢。不过,该警告仍然适用于大多数其他情况!
【解决方案2】:

这是管理动态对象数组的可移植代码。本质上是std::vector:

void * addr = ::operator new(sizeof(Object) * num_elements);
Object * p = static_cast<Object *>(addr);
for (std::size_t i = 0; i != num_elements; ++i)
{
    ::new (p + i) Object(/* some initializer */);
}

// ...

for (std::size_t i = 0; i != num_elements; ++i)
{
    std::size_t ri = num_elements - i - 1;
    (p + ri)->~Object();
}

::operator delete(addr);

如果您想要进行非常低级别的控制,这是您应该如何组织动态存储的一般模式。结果是动态数组不应该成为一种语言特性,并且在库中实现得更好。正如我上面所说,这段代码与名为std::vector&lt;Object&gt; 的现有标准库小工具几乎相同。

【讨论】:

  • 虽然您在 cmets 中回答了我的问题,指出我的代码会在现有平台上中断,但您在这里写的答案并没有真正清楚地回答我的问题。我不会接受它(希望你改进它或者其他人写得清楚),但我仍然赞成你提到将数字参数传递给 new 而不是创建 char 数组的可能性,因为我没有不知道。
猜你喜欢
  • 2020-03-27
  • 2015-09-29
  • 2012-12-17
  • 2021-11-28
  • 1970-01-01
  • 2021-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多