关于c ++:新的数组放置需要缓冲区中未指定的开销吗?

Array placement-new requires unspecified overhead in the buffer?

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.


除非您事先知道此问题的答案,否则不要使用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位体系结构进行编译。


更新:经过讨论,我了解我的答案不再适用于该问题。我将其保留在此处,但仍然需要真正的答案。

如果很快找不到一个好的答案,我会很乐意为这个问题提供支持。

据我所知,我将在这里重述该问题,希望较短的版本可以帮助其他人理解所要提出的问题。问题是:

以下构造始终正确吗? 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()

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

  • 发生的情况是表达式T * p = new T[10];导致:

  • 使用大小参数10 * sizeof(T) + xoperator 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


    正如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时,您需要手动销毁对象并释放内存。


    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.

    这是标准中的缺陷。有传言说他们找不到志愿者为它写一个例外(消息#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一样)