[expr.new]C++的5.3.4 2月草案给出了一个例子:
new(2,f) T[5]导致打电话operator new[](sizeof(T)*5+y,2,f).这里,x和y是非负的未指定值,表示数组分配开销; new-expression的结果将从返回的值中抵消此数量
operator new[].这种开销可以应用于所有数组新表达式,包括那些引用库函数operator new[](std::size_t, void*)和其他放置分配函数的表达式.开销的数量可能因新的一次调用而异.- 末端的例子 ]
现在来看以下示例代码:
void* buffer = malloc(sizeof(std::string) * 10);
std::string* p = ::new (buffer) std::string[10];
Run Code Online (Sandbox Code Playgroud)
根据上面的引用,第二行将new (buffer) std::string[10]在内部调用operator new[](sizeof(std::string) * 10 + y, buffer)(在构造单个std::string对象之前).问题是如果y > 0,预分配的缓冲区太小了!
那么我如何知道在使用数组放置时预先分配多少内存?
void* buffer = malloc(sizeof(std::string) * 10 + how_much_additional_space);
std::string* p = ::new (buffer) std::string[10];
Run Code Online (Sandbox Code Playgroud)
或者标准某处是否保证y == 0在这种情况下?报价再次说:
这种开销可以应用于所有数组新表达式,包括那些引用库函数
operator …
在将它用于数组时,是否可以在便携式代码中实际使用新的放置?
看来你从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;
}
Run Code Online (Sandbox Code Playgroud)
查看内存,编译器似乎使用缓冲区的前四个字节来存储其中项目数的计数.这意味着因为缓冲区sizeof(A)*NUMELEMENTS很大,所以数组中的最后一个元素被写入未分配的堆中.
所以问题是你能找到你的实现需要多少额外的开销来安全地使用placement new []吗?理想情况下,我需要一种可在不同编译器之间移植的技术.请注意,至少在VC的情况下,不同类的开销似乎不同.例如,如果我删除示例中的虚拟析构函数,则new []返回的地址与我传入的地址相同.
在潜入动态记忆的过程中,我发现看起来矛盾的是琐碎的类型如何开始它们的生命.考虑一下片段
void* p = ::operator new(sizeof(int)); // 1
// 2
new (p) int; // 3
Run Code Online (Sandbox Code Playgroud)
什么时候int开始它的生命?
仅获取存储,::operator new指定具有效果(来自[new.delete.single])
new-expression调用的分配函数用于分配大小的存储字节.[...]分配适当对齐的存储空间以表示该大小的任何对象,前提是对象的类型没有新扩展的对齐方式.
鉴于获取存储不足以创建对象,int因此无法在此处开始其生命周期.
此时,int已经获得了适合的存储空间.
这int是由新位置创建的.但不知何故,它的生命没有从这里开始,因为来自[basic.life]
[...]如果一个对象属于类或聚合类型,并且它或其子对象之一由除了普通默认构造函数之外的构造函数初始化,则称该对象具有非空的初始化.类型对象的生命周期从以下时间
T开始:
获得具有适当对齐和类型大小的存储
T,并且如果对象具有非空的初始化,则其初始化完成[...]
int既不是类也不是聚合类型,因此它具有空的初始化.因此,只有第一颗子弹适用.然而,这显然不是在获得存储时,因此不能在其寿命开始时.
分配器需要在不构造其元素的情况下返回内存.然而,这对于琐碎的类型来说没有意义.的影响a.allocate(n)与a对类型的分配器对象T是
n为类型的T对象分配内存,但不构造对象.