可以以便携方式使用新的数组放置吗?

时间:2008-08-18 21:33:36

标签: c++ arrays compiler-construction portability overhead

在将数据库用于数组时,是否可以在便携式代码中实际使用placement new?

看来你从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大,所以数组中的最后一个元素被写入未分配的堆中。

所以问题是,您是否可以找到实施所需的额外开销,以便安全地使用placement new []?理想情况下,我需要一种可在不同编译器之间移植的技术。请注意,至少在VC的情况下,不同类的开销似乎不同。例如,如果我删除示例中的虚拟析构函数,则从new []返回的地址与我传入的地址相同。

7 个答案:

答案 0 :(得分:27)

就个人而言,我选择不在数组上使用placement new,而是单独在数组中的每个项目上使用placement new。例如:

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并确保在每个对象上调用析构函数时,它可以迭代它们。如果它不知道有多少它将无法做到这一点。

答案 1 :(得分:4)

@Derek

5.3.4,第12节讨论了数组分配开销,除非我误读它,否则它似乎向我建议编译器将它添加到新的位置是有效的:

  

此开销可以应用于所有数组新表达式,包括引用库函数operator new [](std :: size_t,void *)和其他放置分配函数的那些表达式。开销的数量可能因新的一次调用而异。

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

答案 2 :(得分:2)

感谢您的回复。对阵列中的每个项目使用placement new是我在遇到这个时最终使用的解决方案(对不起,应该在问题中提到)。我只是觉得我一直缺少一些关于放置new []的事情。实际上,由于标准允许编译器向数组添加额外的未指定开销,因此放置new []似乎基本上无法使用。我不知道你怎么能安全和便携地使用它。

我甚至不清楚为什么它需要额外的数据,因为你不会在数组上调用delete [],所以我不完全明白为什么它需要知道它中有多少项。

答案 3 :(得分:2)

Placement new本身是可移植的,但是您对指定内存块所做的假设是不可移植的。就像之前所说的那样,如果你是一个编译器并且给了一块内存,你怎么知道如何分配一个数组并正确地破坏每个元素,如果你只有一个指针? (参见operator delete []的界面。)

编辑:

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

new []是否真的需要以某种方式跟踪元素的数量,这是由标准产生的,这留给了编译器。不幸的是,在这种情况下。

答案 4 :(得分:2)

@詹姆斯

  

我甚至不清楚为什么它需要额外的数据,因为你不会在数组上调用delete [],所以我不完全明白为什么它需要知道它中有多少项。

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

我还在Mac上用gcc测试了这个,使用了带析构函数的类。在我的系统上,放置new是而不是更改指针。这让我想知道这是否是VC ++问题,以及这是否可能违反标准(标准并没有特别针对此问题,据我所知)。

答案 5 :(得分:1)

与使用单个元素计算一个placement-new的大小类似,使用这些元素的数组来计算数组所需的大小。

如果您需要其他计算的大小,其中元素的数量可能未知,您可以使用sizeof(A [1])并乘以您所需的元素数。

e.g

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

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

答案 6 :(得分:1)

我认为gcc与MSVC做同样的事情,但当然这并不能使它“可移植”。

我认为当NUMELEMENTS确实是编译时常量时,你可以解决这个问题,如下所示:

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

这应该使用新标量放置。