数组放置 – 新需要缓冲区中的未指定的开销?
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内存。
你似乎混淆了一些事情:
-
你的例子调用
operator new[]()
,而不是。operator new()
-
分配函数不会构造任何东西。 他们分配 。
会发生什么是expression式 T * p = new T[10];
原因:
-
调用
operator new[]()
,其大小参数为10 * sizeof(T) + x
, -
十次调用
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[]
兼容,因此带有数组长度的开销。