C ++ 2月11日草案的5.3.4 [expr.new]给出了示例:
new(2,f) T[5] results in a call of operator new[](sizeof(T)*5+y,2,f).
Here, x and y are non-negative unspecified values representing array allocation overhead; the result of the new-expression will be offset by this amount from the value returned by operator 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. —end example ]
现在,使用以下示例代码:
1 2
| 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,则预分配的缓冲区将太小!
那么如何知道使用新数组放置时要预分配多少内存?
1 2
| void* buffer = malloc(sizeof(std::string) * 10 + how_much_additional_space);
std::string* p = ::new (buffer) std::string[10]; |
还是在这种情况下某个地方的标准可以保证y == 0? 再次引用:
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.
-
来自chat.stackoverflow.com/transcript/message/2270516#2270516的问题
-
为了清楚起见," f"是什么?
-
@JaredKrumsie:C ++ 11标准没有阐明。显然,它只是表示任何任意类型的任何任意值。出于这个特定问题的目的,我想它必须代表一个char*。
-
我认为您根本不会知道。我认为新布局总是被认为是一种使用自己的内存管理器的工具,而不是允许您预先分配内存的工具。无论如何,为什么不简单地使用常规new遍历数组呢?我认为这不会对性能产生太大影响,因为new放置基本上是一个禁忌,而且数组中所有对象的构造函数无论如何都必须单独调用。
-
@j_kubik那并不像看起来那么简单!如果其中一个构造函数在循环中途抛出,则必须清理已经构造的对象,某些新数组会为您完成。但是一切似乎都表明不能安全地使用new-placement-array。
-
@FredOverflow:非常感谢您澄清这个问题。
-
x和y额外空间的意义是什么(必须在这里找到x值,因为它不包括在内)?如果是发生异常的原因,则应在标准中说明。如果它是特定于编译器实现的,那么从可移植性的角度来看,这将使其完全无用。
-
@Adrian:大概是空间的要点,以便实现可以告诉要调用多少个析构函数。没有这个未指定的空间,delete[]几乎不可能知道有多少个对象。
-
相同的想法。为了能够确定要销毁多少个对象。但是,由于delete[]要求这样做,因此应在标准中对其包含的内容进行定义,或者至少应将其定义为具有特定属性的类/结构。
-
@Adrian:工具也可以在其中放置其他信息,例如对齐方式或所需的任何信息。如果在标准中定义,则不可能以符合标准的方式正确实施。这就是为什么他们有实现定义的详细信息...
-
的确如此,但是如果所有这些事情都可以在文档中规定,并且具有通过代码确定这些值的方法,那么它将成为可移植的代码。我发现C ++还是某种实验性的语言,所以我认为设计人员不想将自己描绘成一个角落。但这就是不同的利益相关者与设计者之间进行沟通以阻止这种情况发生的原因。
-
@Adrian:以这种方式进行设计也很合理,以便实现可以在一个程序中存储要调用的析构函数的数量,并在同一程序的另一位置存储零开销(如果在其他地方知道该值)。
-
那将是有意义的,而且我认为它是完成的。但是,在这种情况下,operator new[]和operator delete[]的实现细节应该位于它们所位于的任何范围内,以在内部处理这些额外的开销,而不是使此开销与所需的最小空间一起传递。我认为这是最初的意图,但是如果构造函数引发异常,那么如果不知道构造了多少个元素,这可能会引起问题。 C ++真正缺少的是一种定义如何构造元素数组的方法。
-
这种开销可能会应用于所有数组new-expression,包括引用库函数运算符new[](std::size_t, void*) Ugh的那些,那太可怕了(我不确定我是否相信它-毫无意义)。
-
still,仍在最新的标准草案中:eel.is/c++draft/expr.new#15。我仍然不相信。
除非您事先知道此问题的答案,否则不要使用operator new[](std::size_t, void* p)。答案是实现细节,并且可以随编译器/平台而变化。尽管它通常对于任何给定的平台都是稳定的。例如。这是Itanium ABI指定的。
如果您不知道这个问题的答案,请编写新的展示位置数组,以便在运行时进行检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| inline
void*
operator new[](std::size_t n, void* p, std::size_t limit)
{
if (n <= limit)
std::cout <<"life is good
";
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。对于我的平台,y是1个字。 sizeof(word)取决于我是针对32位还是64位体系结构进行编译。
-
还有另一个我从未考虑过的好主意!我相信规格说明可能因呼叫而异,但这可以正确处理!
-
但是,这如何说明对齐方式?偏移量是否保证适合所需的对齐方式?
-
难道仅通过采用(字节广播)指针buffer和p的差来推断y吗?
-
@Kerrek SB:您是正确的,我对对准不小心。香港专业教育学院添加alignas到客户端代码以使事情正确。放置新表达式应注意分别与" cookie"和"数据"对齐。例如,这是Itanium ABI的工作方式(sourcery.mentor.com/public/cxx-abi/abi.html#array-cookies)。是的,您可以按照您的建议推断y。请注意,y可能取决于newd类型的对齐方式以及该类型是否具有琐碎的析构函数(并且其他平台可能具有其他详细信息)。
-
@HowardHinnant:我仍然对展示位置版本根本不需要任何cookie感到困惑。这是为了什么?里面是什么?毕竟,销毁这些数组元素的唯一方法是手工,不是吗?您的链接甚至说,放置版本(size_t, void*)没有cookie。您认为cookie的非零值应该是缺陷报告吗?
-
@Kerrek SB:嗯,这是一个很好的问题,我不确定我对此有一个好的答案。我想假设假想的用户编写的放置删除(如果在每个元素的默认构造期间引发异常的情况下调用)可能会在清理过程中使用cookie。但是我的后兜里没有一个很好的例子。并且即使存在这种假设的用户编写的放置删除,它也必然取决于平台。从好的方面来说,sizeof(y)为0是合法的。:-)
-
如果您要就此提交缺陷报告,则应针对CWG(而不是LWG)。以下是CWG问题列表:open-std.org/jtc1/sc22/wg21/docs/cwg_active.html。提交问题的最佳策略是通过电子邮件发送该列表的作者。我不知道,如果除了与已建立的ABI(例如Itanium ABI)的向后兼容性之外,没有其他原因是否总是要求y == 0的问题会成功。在如此低的水平上打破ABI是非常艰巨的。
-
@HowardHinnant:谢谢!我现在将DR发布到标准邮件列表中,让我们看看它是否超过了主持人!不幸的是,我无法重现y始终为非零的任何情况,但是我无法访问Itanium。
-
看来这里已经是有关此问题的缺陷报告! ......
-
哇,经过7年没有采取任何行动,也许您是由该人撰写所要求的论文! :-)
-
@HowardHinnant:谁,我?我在邮件列表上发表了DR帖子,但到目前为止没有任何回复。
-
@KerrekSB:当编译器决定需要分配的数组长度时,需要使用cookie。请注意:当它决定时,它并不总是存在。值得注意的是,当涉及具有琐碎析构函数的对象时,cookie可能会被省略。任何放置重载都会发生这种情况,这意味着对于数组,应避免所有重载,而不仅仅是void*的"缓冲区"重载。
-
@alecov新数组放置(void* operator new[]( std::size_t count, void* ptr );)不分配内存。它是没有人。
-
@ bit2shift:您的意思是?我没有要求其他。
-
@alecov认为该标准中的缺陷已被忽略了太长时间。
更新:经过讨论,我了解我的答案不再适用于该问题。我将其保留在此处,但仍然需要真正的答案。
如果很快找不到一个好的答案,我会很乐意为这个问题提供支持。
据我所知,我将在这里重述该问题,希望较短的版本可以帮助其他人理解所要提出的问题。问题是:
以下构造始终正确吗? arr == addr结尾吗?
1 2
| void * addr = std::malloc(N * sizeof(T));
T * arr = ::new (addr) T[N]; // #1 |
从标准中我们知道#1导致调用::operator new[](???, addr),其中???是不小于N * sizeof(T)的未指定数字,并且我们还知道该调用仅返回addr并且没有其他影响。我们也知道arr相对于addr的偏移量。我们不知道addr指向的内存是否足够大,或者我们如何知道要分配多少内存。
您似乎混淆了几件事:
您的示例调用operator new[](),而不是 operator new() s>。
分配函数不会构造任何东西。他们分配。
发生的情况是表达式T * p = new T[10];导致:
使用大小参数10 * sizeof(T) + x的operator new[]()调用,
十个调用T的默认构造函数,实际上是::new (p + i) T()。
唯一的特点是,新数组表达式比数组数据本身需要更多的内存。您不会看到任何这些信息,并且只能通过静默接受才能以其他任何方式使用此信息。
如果您对实际分配了多少内存感到好奇,则可以简单地替换数组分配函数operator new[]和operator delete[]并使其打印出实际大小。
更新:作为随机信息,您应注意,全局放置新功能必须为空操作。也就是说,当您像这样就地构造对象或数组时:
1 2
| 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。
-
您应该扩展new和operator new函数之间的区别。在链接到convo之前,我以为new只是语法糖。同样,对operator new而不是operator new[]的调用是错别字。我再次在此评论中做到了:(
-
@MooingDuck:我和其他人在这样做之前做过无数次。我推荐一本好书,或者搜索。
-
但是new(buf) T[10]呢?如何使buf足够大? (通过聊天讨论,我知道这是实际的预期问题,但是并没有明确说明:()
-
@ R.MartinhoFernandes:你绝对正确;我已经修改了答案,基本上我现在还没有答案。除非有人例外,否则我不会删除它,但是我们绝对仍然需要一个正确的答案。
-
为了澄清,我们同意::new(buf) T[n]恰好需要sizeof(T[n])个字节,对吗?并且它的未限定调用new(buf) T[n]是未指定的吗?
-
@GMan:不!相反:我们不知道::new (buf) T[n]需要多少内存!就是5.3.4最初的引用所说的话:我们叫::operator new[](sizeof(T) * n + y, buf),对y不了解。
-
@KerrekSB:哦,我现在看到了。谢谢。
-
@KerrekSB:我认为您的回答存在矛盾。首先,您对T * arr = ::new (addr) T[N];说We also know that arr is offset from addr correspondingly,然后对T * arr = ::new (buf10) T[10];说arr == buf10 ...是什么?
-
@etherice:您说得对,我不确定我为什么写那封信。全局布局分配功能是禁止操作的,但是您无法控制所需的空间量。因此buf10需要指向10 * sizeof(T) + y字节的内存,但是您不知道y。修改错误。
正如Kerrek SB在评论中提到的那样,此缺陷于2004年首次报告,并于2012年通过以下方式解决:
The CWG agreed that EWG is the appropriate venue for dealing with this issue.
然后,该缺陷在2013年报告给EWG,但是以NAD(大概是" Not A Defect")关闭,并带有以下注释:
The problem is in trying to use array new to put an array into pre-existing storage. We don't need to use array new for that; just construct them.
这大概意味着建议的解决方法是使用一个循环,对构造的每个对象使用一次对非数组放置的调用。
线程其他地方未提及的一个推论是,此代码导致所有T的行为未定义:
1 2
| T *ptr = new T[N];
::operator delete[](ptr); |
即使我们遵守生存期规则(即T要么具有微不足道的销毁,或者程序不依赖于析构函数的副作用),问题仍然在于ptr已针对此未指定的cookie进行了调整,因此错误的值传递给operator delete[]。
在固定大小的存储区中,调用任何版本的operator new[] ()都不能很好地工作。本质上,假定它委托给一些实际的内存分配函数,而不仅仅是返回指向分配的内存的指针。如果已经有一个要构造对象数组的内存空间,则要使用std::uninitialized_fill()或std::uninitialized_copy()来构造对象(或单独构造对象的某种其他形式)。
您可能会争辩说,这意味着您还必须手动销毁内存中的对象。但是,在从放置new返回的指针上调用delete[] array无效:它将使用operator delete[] ()的非放置版本!也就是说,使用放置new时,您需要手动销毁对象并释放内存。
-
关于放置运算符delete []()的要点。 @Mooing Duck:注意这一点。
-
我知道必须手动删除新放置的对象。 uninitialized_fill是一个好主意,但您似乎要说的是,在C ++规范中使用数组的数组的重载运算符new不适用于其设计目的。那是你的意思吗? (这就是聊天确定的。)
-
放置运算符new []()可以达到预期的目的:使用其他参数分配内存并在此内存中构造对象。似乎无法移植的是仅对已分配的内存使用void *的版本。鉴于您将不知道对象最终在哪里,无论如何似乎还是有问题的。
-
整个问题在于,只有标准delete[]运算符需要存储在额外字节中的信息(用于遍历数组,调用每个元素析构函数,以及用于将数组的大小传递给释放函数)(如果需要的话)需要它)。现在对我来说有趣的问题是该标准是否实际上是这样说的,或者我们是否发现了缺陷。
-
我不认为这有缺陷。但是,我同意可以对标准进行增强,以消除使用超出对象所需内存的内存的可能性。
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.
blockquote>
这是标准中的缺陷。有传言说他们找不到志愿者为它写一个例外(消息#1173)。
不可替换的数组placement-new不能与delete[]表达式一起使用,因此您需要遍历数组并调用每个析构函数。
开销的目标是用户定义的数组placement-new函数,该函数与常规T* tp = new T[length]一样分配内存。那些与delete[]兼容,因此开销占用了数组长度。
在阅读了相应的标准部分之后,我很想知道对数组类型进行new放置根本没用,而标准允许它的唯一原因是描述new-operator的通用方式:
The new expression attempts to create an object of the typeid (8.1) or
newtypeid to which it is applied. The type of that object is the
allocated type. This type shall be a complete object type, but not an
abstract class type or array thereof (1.8, 3.9, 10.4). [Note: because
references are not objects, references cannot be created by
newexpressions. ] [Note: the typeid may be a cvqualified type, in
which case the object created by the newexpression has a cvqualified
type. ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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仅仅是出于定义的紧凑性(所有可能的用途都作为一种方案),并且似乎没有充分的理由禁止它。
这使我们处于一种无用的运算符的情况,该运算符需要先分配内存,然后才能知道将需要多少内存。我看到的唯一解决方案是要么整体分配内存,并希望编译器不希望提供过多的内存,要么希望在重写的array placement new函数/方法中重新分配内存(这首先会破坏使用array placement new的目的) )。
要回答Kerrek SB指出的问题:
你的例子:
1 2
| void * addr = std::malloc(N * sizeof(T));
T * arr = ::new (addr) T[N]; // #1 |
并不总是正确的。在大多数实现中,arr!=addr(并且有充分的理由),因此您的代码无效,并且缓冲区将被溢出。
关于这些"正当理由"-请注意,使用array new运算符时,标准创建者将您从某些内部管理中释放出来,并且array placement new在这方面没有什么不同。注意,您不需要通知delete[]有关数组的长度,因此此信息必须保留在数组本身中。哪里?正是在这个额外的内存中。没有它,delete[]'ing将需要保持数组长度分开(就像stl使用循环和非放置new一样)
-
但是,没有放置删除操作,因此最后一个参数实际上不起作用...
-
这是真的,但是我猜是否放置它仍应该在内存中产生二进制相同的结构。
-
至少不是!二进制结构在任何地方都没有强制性要求,对于所有标准数组新闻来说,二进制结构也不相同-而是取决于类型。