可以以可移植的方式使用阵列的新放置吗?

时间:2020-03-05 18:40:32  来源:igfitidea点击:

将其用于数组时,是否可以实际利用可移植代码中的新放置?

看来我们从new []返回的指针并不总是与我们传递的地址相同(5.3.4,标准中的注12似乎确认这是正确的),但是我看不到我们如何在这种情况下,可以为数组分配一个缓冲区。

以下示例显示了该问题。与Visual Studio一起编译,此示例导致内存损坏:

#include <new>
#include <stdio.h>

class A
{
    public:

    A() : data(0) {}
    virtual ~A() {}
    int data;
};

int main()
{
    const int NUMELEMENTS=20;

    char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
    A *pA = new(pBuffer) A[NUMELEMENTS];

    // With VC++, pA will be four bytes higher than pBuffer
    printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);

    // Debug runtime will assert here due to heap corruption
    delete[] pBuffer;

    return 0;
}

从内存看,编译器似乎正在使用缓冲区的前四个字节来存储其中的项目数计数。这意味着因为缓冲区只有sizeof(A)* NUMELEMENTS大,所以数组中的最后一个元素被写入未分配的堆中。

因此,问题是我们能找出为了安全地使用位置new []而实现需要多少额外开销吗?理想情况下,我需要一种可在不同编译器之间移植的技术。请注意,至少在VC的情况下,不同类的开销似乎有所不同。例如,如果我在示例中删除了虚拟析构函数,则new []返回的地址与我传入的地址相同。

解决方案

回答

我认为gcc与MSVC的作用相同,但是当然这并不能使其"可移植"。

我认为当NUMELEMENTS确实是一个编译时间常数时,我们可以解决该问题,如下所示:

`
typedef A Arr [NUMELEMENTS];

A * p =新的(缓冲区)Arr;
`

这应该使用新的标量放置。

回答

就我个人而言,我可以选择不在数组上使用新的展示位置,而是单独在数组中的每个项目上使用新的展示位置。例如:

int main(int argc, char* argv[])
{
  const int NUMELEMENTS=20;

  char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
  A *pA = (A*)pBuffer;

  for(int i = 0; i < NUMELEMENTS; ++i)
  {
    pA[i] = new (pA + i) A();
  }

  printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);

  // dont forget to destroy!
  for(int i = 0; i < NUMELEMENTS; ++i)
  {
    pA[i].~A();
  }    

  delete[] pBuffer;

  return 0;
}

无论使用哪种方法,请确保在删除pBuffer之前手动销毁数组中的每个项,否则可能会导致泄漏;)

注意:我还没有编译它,但是我认为它应该可以工作(我在没有安装C ++编译器的机器上)。它仍然表明了要点:)希望它能以某种方式有所帮助!

编辑:

之所以需要跟踪元素的数量,是因为当我们在数组上调用delete并确保在每个对象上都调用了析构函数时,它可以遍历它们。如果不知道有多少个,它将无法执行此操作。

回答

与使用单个元素计算一个新放置的大小的方法类似,使用这些元素的数组计算一个数组所需的大小。

如果我们需要大小用于可能不知道元素数量的其他计算,则可以使用sizeof(A [1])并乘以所需的元素数。

例如

char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ];
A *pA = (A*)pBuffer;

for(int i = 0; i < NUMELEMENTS; ++i)
{
    pA[i] = new (pA + i) A();
}

回答

感谢答复。当我遇到这个问题时,我最终使用的解决方案是为数组中的每个项目使用new布局(对不起,应该在问题中提到)。我只是觉得在放置new []时一定缺少一些东西。照原样,由于标准允许编译器向数组添加额外的未指定开销,因此似乎无法使用new []放置。我看不到如何安全,便携地使用它。

我什至还不清楚为什么它需要额外的数据,因为无论如何我们都不会在数组上调用delete [],所以我不完全明白为什么它需要知道其中有多少个项目。

回答

@詹姆士

I'm not even really clear why it needs the additional data, as you wouldn't call delete[] on the array anyway, so I don't entirely see why it needs to know how many items are in it.

经过考虑后,我同意你的看法。由于没有放置删除,因此没有理由为什么放置新的位置需要存储元素的数量。由于没有删除放置,因此没有理由放置新的存储元素的数量。

我还在Mac上使用带有析构函数的类在gcc上对此进行了测试。在我的系统上,new位置没有更改指针。这使我想知道这是否是VC ++问题,以及是否可能违反该标准(据我所知,该标准并未专门解决此问题)。

回答

@Derek

5.3.4的第12节讨论了数组分配的开销,除非我误读了它,否则似乎暗示我编译器也可以将其添加到new放置上是有效的:

This overhead may be applied in all array new-expressions, including those referencing the library function operator new[](std::size_t, void*) and other placement allocation functions. The amount of overhead may vary from one invocation of new to another.

就是说,我认为VC是唯一给我带来麻烦的编译器,其中包括GCC,Codewarrior和ProDG。不过,我必须再次检查以确保。

回答

放置新本身本身是可移植的,但是我们对它对指定内存块所做的假设不是可移植的。就像之前所说的,如果我们是一个编译器并被分配了一块内存,那么如果我们拥有的只是一个指针,那么我们如何知道如何分配一个数组并正确地破坏每个元素呢? (请参阅运算符delete []的界面。)

编辑:

实际上有一个放置删除,只有在构造函数使用放置位置new []分配数组时,构造函数抛出异常时才调用它。

new []是否实际上确实需要以某种方式跟踪元素的数量,这取决于标准,由标准决定。不幸的是,在这种情况下。