关于c ++:可以以可移植的方式使用新的数组放置方式吗?

关于c ++:可以以可移植的方式使用新的数组放置方式吗?

Can placement new for arrays be used in a portable way?

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#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
"
, pBuffer, pA);

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

    return 0;
}

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
"
, pBuffer, pA);

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

  delete[] pBuffer;

  return 0;
}

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

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

编辑:

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


@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 []是否实际上需要以某种方式跟踪元素的数量,这取决于标准,由标准决定。不幸的是,在这种情况下。


@詹姆士

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 ++问题,以及是否可能违反该标准(据我所知,该标准并未专门解决此问题)。


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

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


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

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

例如

1
2
3
4
5
6
7
char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ];
A *pA = (A*)pBuffer;

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


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

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


typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

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


推荐阅读