66

5.3.4 [expr.new] of the C++11 Feb draft gives the example:

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 ]

Now take the following example code:

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

According to the above quote, the second line new (buffer) std::string[10] will internally call operator new[](sizeof(std::string) * 10 + y, buffer) (before constructing the individual std::string objects). The problem is that if y > 0, the pre-allocated buffer will be too small!

So how do I know how much memory to pre-allocate when using array placement-new?

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

Or does the standard somewhere guarantee that y == 0 in this case? Again, the quote says:

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.

4

7 回答 7

48

更新

Nicol Bolas 在下面的评论中正确地指出,这已得到修复,因此开销始终为零operator new[](std::size_t, void* p)

此修复是在 2019 年 11 月作为缺陷报告完成的,这使其可追溯至所有 C++ 版本。

原始答案

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您的平台。对于我的平台 y是 1 个字。sizeof(word) 取决于我是针对 32 位还是 64 位架构进行编译。

于 2012-01-04T04:13:38.313 回答
9

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

如果没有很快找到一个好的答案,我很乐意用一些赏金来支持这个问题。

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

下面的结构总是正确的吗?是arr == addr在最后吗?

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足够大,或者我们如何知道要分配多少内存。


您似乎混淆了一些事情:

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

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

会发生什么是表达式 T * p = new T[10];导致:

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

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

唯一的特点是 array-new表达式要求的内存比数组数据本身使用的更多。您看不到任何这些,也不能以任何方式使用这些信息,除非是默默接受。


如果您想知道实际分配了多少内存,您可以简单地替换数组分配函数operator new[]operator delete[]使其打印出实际大小。


更新:作为一条随机信息,您应该注意全局放置新功能必须是无操作的。也就是说,当您像这样就地构造对象或数组时:

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.

于 2012-01-04T00:05:11.347 回答
7

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

CWG 同意 EWG 是处理此问题的合适场所。

然后在 2013 年向 EWG 报告了该缺陷,但以 NAD(大概意思是“不是缺陷”)的形式关闭,并附有以下评论:

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

这大概意味着建议的解决方法是使用一个循环,并为每个正在构造的对象调用一次非数组放置 new。


线程其他地方未提及的推论是此代码导致所有未定义的行为T

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

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

于 2016-03-10T20:42:35.983 回答
6

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

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

于 2012-01-04T05:58:38.167 回答
3

请注意,C++20 改变了这个答案。

C++17(及之前)[expr.new]/11清楚地表明这个函数可能会得到一个实现定义的偏移量:

当 new 表达式调用分配函数并且该分配尚未扩展时,new 表达式将请求的空间量作为 std​::​size_t 类型的第一个参数传递给分配函数。该参数不应小于正在创建的对象的大小;只有当对象是一个数组时,它才可能大于正在创建的对象的大小。

这允许但不要求分配给数组分配函数的大小可以从 增加sizeof(T) * size

C++20 明确禁止这样做。来自[expr.new]/15

当 new 表达式调用分配函数并且该分配尚未扩展时,new 表达式将请求的空间量作为 std​::​size_t 类型的第一个参数传递给分配函数。该参数不应小于正在创建的对象的大小;仅当对象是数组并且分配函数不是非分配形式([new.delete.placement])时,它才可能大于正在创建的对象的大小。

重点补充。甚至您引用的非规范性说明也已更改:

这种开销可以应用于所有数组 new 表达式,包括那些引用布局分配函数的表达式,除非引用库函数 operator new[](std​::​size_t, void*)。

于 2021-03-21T19:21:35.867 回答
1

在阅读了相应的标准部分之后,我很高兴地认为将 new 放置在数组类型中简直是无用的想法,标准允许它的唯一原因是描述 new-operator 的通用方式:

new 表达式尝试创建应用它的 typeid (8.1) 或 newtypeid 的对象。该对象的类型是分配的类型。此类型应为完整的对象类型,但不是抽象类类型或其数组(1.8、3.9、10.4)。[注意:因为引用不是对象,引用不能由 newexpressions 创建。] [注意:typeid 可能是 cvqualified 类型,在这种情况下,由 new 表达式创建的对象具有 cvqualified 类型。]

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 指出的问题:您的示例:

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

并不总是正确的。在大多数实现arr!=addr中(并且有充分的理由),因此您的代码无效,并且您的缓冲区将溢出。

关于那些“充分的理由” - 请注意,您在使用array new运算符时被标准创建者从一些家务中释放出来,array placement new在这方面没有什么不同。请注意,您不需要告知delete[]数组的长度,因此该信息必须保存在数组本身中。在哪里?正是在这个额外的内存中。如果没有它,则delete[]需要保持数组长度分开(就像 stl 使用循环和非放置一样new

于 2012-01-04T22:54:54.663 回答
1

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

这是标准中的一个缺陷。有传言说他们找不到志愿者为其写一个例外(消息 #1173)。

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

开销针对用户定义的数组放置新函数,它像常规的T* tp = new T[length]. 那些与 兼容delete[],因此承载数组长度的开销。

于 2016-04-26T03:51:51.500 回答