在将可移植代码中的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[]返回的地址与我传入的地址相同。
8条答案
按热度按时间aurhwmvo1#
就我个人而言,我会选择不在数组上使用placement new,而是单独在数组中的每个项目上使用placement new。
不管使用哪种方法,在删除pBuffer之前,请确保手动销毁数组中的每一项,因为可能会导致泄漏;)
编辑:
它需要跟踪元素数量的原因是当你对数组调用delete时,它可以遍历这些元素,并确保对每个对象都调用了析构函数,如果它不知道有多少元素,它就无法做到这一点.
rqmkfv5c2#
@德里克
5.3.4第12节讨论了数组分配开销,除非我误解了,否则它似乎暗示编译器也可以在placement new上添加它:
此开销可能会应用于所有数组new表达式,包括引用库函数运算符new[](std::size_t,void*)和其他位置分配函数的表达式。开销量可能因new的调用而异。
也就是说,我认为VC是唯一一个给我带来麻烦的编译器,除了它,GCC,Codewarrior和ProDG。
zyfwsgd63#
@詹姆斯
我甚至不太清楚为什么它需要额外的数据,因为无论如何都不会对数组调用delete[],所以我完全不明白为什么它需要知道数组中有多少项。
经过一番思考,我同意你的观点,没有理由让placement new存储元素的数量,因为没有placement delete,也没有理由让placement new存储元素的数量。
我也在Mac上用gcc测试了这个,使用了一个带析构函数的类。在我的系统上,placement new没有改变指针。这让我想知道这是否是VC++的问题,是否违反了标准(据我所知,标准并没有特别提到这个问题)。
yqyhoc1h4#
感谢您的回复。对数组中的每一项使用placement new是我遇到这个问题时最终使用的解决方案(对不起,应该在问题中提到这一点)。我只是觉得用placement new []做这件事一定有什么我遗漏的地方。事实上,看起来placement new []基本上是不可用的,这要归功于标准允许编译器向数组添加额外的未指定开销。I don "我看不出你怎么能安全、轻便地使用它。
我甚至不太清楚为什么它需要额外的数据,因为无论如何都不会对数组调用delete [],所以我完全不明白为什么它需要知道数组中有多少项。
iszxjhcz5#
placement new本身是可移植的,但是你对它在指定内存块上的作用所做的假设是不可移植的,就像前面所说的,如果你是一个编译器,并且被分配了一个内存块,如果你只有一个指针,你怎么知道如何分配数组和正确地析构每个元素呢?(参见operator delete []的接口)
编辑:
实际上还有一个placement delete,只不过它只在构造函数用placement new []分配数组时抛出异常时调用。
new []是否真的需要以某种方式跟踪元素的数量是由标准决定的,这就由编译器决定了。不幸的是,在这种情况下。
wj8zmpe16#
与使用单个元素计算一个placement-new的大小类似,使用这些元素的数组计算数组所需的大小。
如果您需要元素数量未知的其他计算的大小,可以使用sizeof(A[1])并乘以所需的元素计数。
例如
p4rjhz4m7#
我认为gcc和MSVC做的是一样的事情,但是当然这并不能使它变得“可移植”。
我认为,当NUMELENTS确实是一个编译时常量时,您可以解决这个问题,如下所示:
typedef A Arr[NUMELEMENTS];
``
A* p = new (buffer) Arr;
这应该使用标量放置new。
xzlaal3s8#
C17(草案N4659)在[expr.new]第15段中指出:
[O]verhead可应用于所有数组 new-expressions,包括引用库函数
operator new[](std::size_t, void*)
和其他位置分配函数的数组 * new-expressions *。开销量可能因new
的调用而异。因此,在C17(及更早版本)中安全地使用
(void*)
布局new[]
似乎是不可能的,而且我也不清楚为什么它甚至被指定存在。在C20(草案N4861)中,这被更改为
[O]verhead可应用于所有数组 new-expressions,包括引用位置分配函数的数组,但引用库函数
operator new[](std::size_t, void*)
时除外。开销量可能因new
的调用而异。因此,如果您确定您使用的是C20,那么您可以安全地使用它--但只能使用这一种放置形式,并且只有在您不覆盖标准定义的情况下(它才出现)。
即使是C++20文本也显得很可笑,因为额外空间的唯一用途是存储数组大小的元数据,但在使用
new[]
的任何自定义放置形式时都无法访问它。它是一种私有格式,只有delete[]
知道如何读取-而使用自定义分配时,您无法使用delete[]
,因此充其量只是浪费空间。实际上,就我所知,根本没有安全的方法来使用
operator new[]
的自定义表单,也没有办法正确调用析构函数,因为必要的信息没有传递给operator new[]
,即使您知道对象是可以轻易析构的,new
表达式可能返回指向operator new[]
返回的内存块中间的某个任意位置的指针(跳过无意义的元数据),所以不能 Package 只提供malloc
和free
等价项的分配库:它还需要一种通过指向块中间的指针来搜索块的方式,即使块存在,这种方式也可能要慢得多。我不明白他们(或者仅仅是Stroustrup?)怎么会把这个搞得这么糟。显然正确的方法是把数组元素的数量和每个元素的大小作为两个参数传递给
operator new[]
,让每个分配器选择如何存储它。也许我漏掉了什么。