数组placement-new需要缓冲区中未指定的开销?

Moo*_*uck 62 c++ standards memory-management placement-new

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

new(2,f) T[5]导致打电话operator new[](sizeof(T)*5+y,2,f).

这里,x和y是非负的未指定值,表示数组分配开销; new-expression的结果将从返回的值中抵消此数量operator new[].这种开销可以应用于所有数组新表达式,包括那些引用库函数operator new[](std::size_t, void*)和其他放置分配函数的表达式.开销的数量可能因新的一次调用而异.- 末端的例子 ]

现在来看以下示例代码:

void* buffer = malloc(sizeof(std::string) * 10);
std::string* p = ::new (buffer) std::string[10];
Run Code Online (Sandbox Code Playgroud)

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

那么我如何知道在使用数组放置时预先分配多少内存?

void* buffer = malloc(sizeof(std::string) * 10 + how_much_additional_space);
std::string* p = ::new (buffer) std::string[10];
Run Code Online (Sandbox Code Playgroud)

或者标准某处是否保证y == 0在这种情况下?报价再次说:

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

How*_*ant 43

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

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

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];
}
Run Code Online (Sandbox Code Playgroud)

通过改变数组大小并n在上面的示例中进行检查,您可以推断出y您的平台.对于我的平台 y是1个字.sizeof(word)取决于我是在编译32位还是64位架构.

  • @HowardHinnant:我仍然感到困惑,*placement*版本需要任何cookie.这是为了什么?里面是什么?毕竟,*only*方式可以破坏那些数组元素,不是吗?你的链接甚至说放置版本`(size_t,void*)`没有cookie.您认为cookie的非中庸者应该是缺陷报告吗? (5认同)
  • 看来这里已经是[缺陷报告](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#476)!D'哦... (4认同)
  • @Kerrek SB:这是一个很好的问题,我不确定我有一个很好的答案.我想一些假设的用户编写的放置删除,如果在每个元素的默认构造期间抛出异常,则调用它可能在清理期间使用cookie.但是我的后袋里没有这种情况的好例子.即使存在这种假设的用户编写的放置删除,它也必然是依赖于平台的.从好的方面来说,sizeof(y)为0是合法的.:-) (2认同)
  • 如果您想提交有关此问题的缺陷报告,则应针对CWG(而不是LWG).以下是CWG问题清单:http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html.提交问题的最佳策略是通过电子邮件发送该列表的作者.我不知道如果除了与已建立的ABI(如Itanium ABI)的向后兼容性之外没有其他原因,那么要求"y == 0"的问题总是会成功.将ABI打破这个低水平是非常艰巨的. (2认同)

Ker*_* SB 8

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

如果很快找不到好的答案,我会很乐意以一些赏金来支持这个问题.

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

以下结构总是正确的吗?是arr == addr在结束了吗?

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

我们从标准中知道#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[]()size参数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];
Run Code Online (Sandbox Code Playgroud)

然后相应的调用::operator new(std::size_t, void*)::operator new[](std::size_t, void*)什么都不做,但返回他们的第二个参数.但是,你不知道buf10应该指出什么:它需要指向10 * sizeof(T) + y内存的字节,但你无法知道y.

  • 但是那个`new(buf)T [10]怎么样?你怎么让`buf`足够大?(来自聊天讨论,我知道这是*实际的预期问题*,但它没有说清楚:() (3认同)
  • @GMan:不!相反:我们不知道`:: new(buf)T [n]`需要多少内存!这就是5.3.4的初始引用所说的:我们调用`:: operator new [](sizeof(T)*n + y,buf)`,不知道`y`. (3认同)

Die*_*ühl 6

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

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

  • placement operator new []()正在按预期工作:使用附加参数以及在此内存中构造对象的方式分配内存.似乎没有可移植性的是只对已经分配的内存采用void*的版本.鉴于你不知道物体最终在哪里,无论如何它似乎都值得怀疑. (2认同)
  • 要点是,只有标准的“delete[]”运算符需要存储在额外字节中的信息(既用于遍历数组,调用每个元素的析构函数,又用于将数组的大小传递给释放函数,如果需要的话)。对我来说,现在有趣的问题是标准是否真的这么说,或者我们是否发现了缺陷。 (2认同)

M.M*_*M.M 6

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

CWG同意EWG是处理此问题的适当场所.

然后在2013年向EWG报告了这个缺陷,但是作为NAD关闭(可能意味着"不是缺陷")评论:

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

这大概是指所建议的解决办法是使用一个循环带,以非阵列放置为正在构建的每个对象的新一次呼叫.


线程上其他地方没有提到的推论是,这段代码导致所有人的未定义行为T:

T *ptr = new T[N];
::operator delete[](ptr);
Run Code Online (Sandbox Code Playgroud)

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