数组放置 – 新需要缓冲区中的未指定的开销?

5.3.4 C ++ 11月2日草案的[expr.new]给出了例子:

new(2,f) T[5]导致operator new[](sizeof(T)*5+y,2,f)的调用。

这里,x和y是表示数组分配开销的非负非指定值; 新expression式的结果将会被operator new[]返回的值所抵消。 这个开销可以应用于所有数组的新expression式 ,包括那些引用库函数operator new[](std::size_t, void*)和其他位置分配函数的expression式。 从一个新的调用到另一个调用的开销可能不同。 – 例子 ]

现在拿下面的例子代码:

 void* buffer = malloc(sizeof(std::string) * 10); std::string* p = ::new (buffer) std::string[10]; 

根据上面的引用,第二行new (buffer) std::string[10]将在内部调用operator new[](sizeof(std::string) * 10 + y, buffer) std::string对象)。 问题是,如果y > 0 ,预分配的缓冲区将太小!

那么如何知道在使用array-new时需要预先分配多less内存呢?

 void* buffer = malloc(sizeof(std::string) * 10 + how_much_additional_space); std::string* p = ::new (buffer) std::string[10]; 

或者标准的地方保证y == 0在这种情况下? 再次,报价说:

这个开销可以应用于所有数组的新expression式 ,包括那些引用库函数operator new[](std::size_t, void*)和其他位置分配函数的expression式。

不要使用operator new[](std::size_t, void* p)除非事先知道这个问题的答案。 答案是一个实现细节,可以通过编译器/平台改变。 虽然它对于任何给定的平台通常是稳定的。 例如,这是由Itanium ABI指定的。

如果您不知道这个问题的答案,请编写您自己的可以在运行时检查的位置数组new:

 inline void* operator new[](std::size_t n, void* p, std::size_t limit) { if (n <= limit) std::cout << "life is good\n"; else throw std::bad_alloc(); return p; } int main() { alignas(std::string) char buffer[100]; std::string* p = new(buffer, sizeof(buffer)) std::string[3]; } 

通过在上面的例子中改变数组大小和检查n ,你可以推断出你的平台。 对于我的平台 y是1个字。 sizeof(word)取决于我是在编译32位还是64位体系结构。

更新:经过一番讨论,我明白我的答案不再适用于这个问题。 我会把它留在这里,但真正的答案肯定还是要求的。

如果不能很快find好的答案,我会很乐意用一些赏金来支持这个问题。

据我所知,我将在这里重申这个问题,希望缩短版本可以帮助其他人理解所要求的内容。 问题是:

以下结构是否正确? 是arr == addr在最后?

 void * addr = std::malloc(N * sizeof(T)); T * arr = ::new (addr) T[N]; // #1 

我们从标准知道#1导致call ::operator new[](???, addr) ,其中??? 是一个不小于N * sizeof(T)的非指定数字,我们也知道那个调用只返回addr ,没有其他的效果。 我们也知道arr是相对于addr偏移量。 我们知道addr指向的内存是否足够大,或者我们如何知道要分配多less内存。


你似乎混淆了一些事情:

  1. 你的例子调用operator new[]() ,而不是operator new()

  2. 分配函数不会构造任何东西。 他们分配

会发生什么是expression式 T * p = new T[10]; 原因:

  1. 调用operator new[]() ,其大小参数为10 * sizeof(T) + x

  2. 十次调用T的默认构造函数,有效::new (p + i) T()

唯一的特点是数组新的expression式要求比数组数据本身使用的更多的内存。 您没有看到这些信息,也不能以无声的接受方式使用这些信息。


如果您好奇实际分配了多less内存,则可以简单地将数组分配函数operator new[]operator delete[]replace,并将其打印出实际大小。


更新:作为一个随机的信息,你应该注意到,全局布局 – 新function被要求是空操作。 也就是说,当你像这样就地构造一个对象或数组时:

 T * p = ::new (buf1) T; T * arr = ::new (buf10) T[10]; 

然后,对::operator new(std::size_t, void*)::operator new[](std::size_t, void*)的相应调用除了返回第二个参数外什么也不做。 但是,你不知道buf10应该指向什么:它需要指向10 * sizeof(T) + y字节的内存,但是你不能知道y

调用任何版本的operator new[] ()对于固定大小的内存区域都不能很好地工作。 本质上,它假定它委托给一些真正的内存分配函数,而不是仅仅返回一个指向分配内存的指针。 如果你已经有了一个内存区域来构build一个对象数组,你需要使用std::uninitialized_fill()或者std::uninitialized_copy()来构造对象(或者其他forms的单独构造对象)。

你可能会争辩说,这意味着你必须手动销毁记忆体中的物体。 但是,从放置new返回的指针调用delete[] array将不起作用:它将使用operator delete[] ()的非放置版本! 也就是说,当使用new位置时,您需要手动销毁对象并释放内存。

正如Kerrek SB在评论中提到的,这个缺陷在2004年首次被报道,并且在2012年被解决为:

CWG同意EWG是处理这个问题的适当场所。

然后在2013年向EWG报告了这个缺陷,但是由于NAD(大概意思是“不是缺陷”)而closures:

问题在于尝试使用array new将数组放入预先存在的存储中。 我们不需要使用新的数组; 只是构build它们。

这大概意味着build议的解决方法是使用一个循环调用每个对象的非数组位置new一次构造。


在线程中没有提到的一个必然结果是这个代码导致所有T未定义的行为:

 T *ptr = new T[N]; ::operator delete[](ptr); 

即使我们遵守生命周期规则(即, T要么具有微不足道的破坏,要么程序不依赖于析构函数的副作用),问题是ptr已经针对这个未指定的cookie进行了调整,所以这是错误的值传递给operator delete[]

在阅读完相应的标准章节之后,我已经习惯性地认为,对于数组types来说,新的放置方式根本就是无用的想法,标准允许的唯一原因就是描述新操作符的通用方法:

新的expression式试图创build它所应用的typeid(8.1)或newtypeid的一个对象。 该对象的types是分配的types。 这个types应该是一个完整的对象types,而不是一个抽象的types或者它的数组(1.8,3.9,10.4)。 [注意:因为引用不是对象,所以引用不能由newexpressions创build。 ] [注意:typeid可能是一个cvqualifiedtypes,在这种情况下,由newexpression创build的对象具有cvqualifiedtypes。 ]

 new-expression: ::(opt) new new-placement(opt) new-type-id new-initializer(opt) ::(opt) new new-placement(opt) ( type-id ) new-initializer(opt) new-placement: ( expression-list ) newtypeid: type-specifier-seq new-declarator(opt) new-declarator: ptr-operator new-declarator(opt) direct-new-declarator direct-new-declarator: [ expression ] direct-new-declarator [ constant-expression ] new-initializer: ( expression-list(opt) ) 

对我来说, array placement new看起来简单地来自于定义的紧凑性(所有可能的用法作为一个scheme),似乎没有理由被禁止。

这使我们处于一个无用的运营商的情况,这个运营商需要先分配内存,然后才知道需要多less内存。 我看到的唯一的解决scheme是要么过度分配内存,并希望编译器不会希望超过提供,或重新分配内存覆盖array placement new函数/方法(这倒是摆脱了首先使用array placement new的目的)。


回答Kerrek SB指出的问题:你的例子:

 void * addr = std::malloc(N * sizeof(T)); T * arr = ::new (addr) T[N]; // #1 

并不总是正确的。 在大多数的实现中, arr!=addr (并且有很好的理由),所以你的代码是无效的,你的缓冲区将会被超载。

关于那些“很好的理由” – 请注意,使用array new操作符时,标准的创build者从一些内务pipe理系统中释放出来,而在这方面array placement new并没有什么不同。 请注意,您不需要通知有关数组长度的delete[] ,所以这些信息必须保存在数组本身中。 哪里? 正是在这个额外的记忆。 没有它delete[] 'ing将需要保持数组长度分开(因为STL使用循环和非放置new

这个开销可以应用于所有数组的新expression式 ,包括那些引用库函数operator new[](std::size_t, void*)和其他位置分配函数的expression式。

这是标准中的缺陷。 有传言说, 他们找不到志愿者写一个例外 (Message#1165)。

不可replace的数组placement-new不能与delete[]expression式一起使用,因此您需要遍历数组并调用每个析构函数

开销是针对用户定义的数组放置 – 新函数,它们像正则T* tp = new T[length]一样分配内存。 这些都与delete[]兼容,因此带有数组长度的开销。

Interesting Posts