新广告刊登位置的开销[]

时间:2018-07-16 07:44:25

标签: c++

当前标准草案明确states表示放置new[]可能会有空间开销:

  

此开销可能会应用于所有数组new-expression,包括引用库函数operator new [](std ::: size_­t,void *)和其他布局分配函数的那些表达式。从一次新调用到另一次调用,开销可能会有所不同。

因此大概他们有一些想法,为什么编译器需要这种开销。它是什么?编译器可以将此开销用于任何有用的事情吗?

据我了解,要破坏此数组,唯一的解决方案是在循环中调用析构函数(我对吗?),因为没有放置delete[](顺便说一句,我们不应该放置delete[]来正确地破坏数组,而不仅仅是数组的元素?)。因此,编译器不必知道数组长度。

我认为,由于这种开销无法用于任何有用的东西,编译器不会使用它(因此,这实际上不是问题)。我已经用以下简单代码检查了编译器:

#include <stdio.h>
#include <new>

struct Foo {
    ~Foo() { }
};

int main() {
    char buffer1[1024];
    char buffer2[1024];

    float *fl = new(buffer1) float[3];
    Foo *foo = new(buffer2) Foo[3];

    printf("overhead for float[]: %d\n", (int)(reinterpret_cast<char*>(fl) - buffer1));
    printf("overhead for Foo[]  : %d\n", (int)(reinterpret_cast<char*>(foo) - buffer2));
}

GCC和clang根本不使用任何开销。但是,对于Foo,MSVC使用8个字节。 MSVC可以出于何种目的使用此开销?


这里有一些背景,为什么我要问这个问题。

以前有关于此主题的问题:

据我所知,这些问题的实质是避免循环使用放置new[],而使用循环new。但是此解决方案不会创建数组,但是使用operator[]彼此相邻的元素(不是数组)对它们而言是未定义的行为。这些问题更多地与如何避免放置new[]有关,但这个问题更多地与“为什么?”有关。

4 个答案:

答案 0 :(得分:2)

  

当前标准草案明确指出...

为澄清起见,此规则(可能)自该标准的第一个版本(我可以访问的最早版本是C ++ 03)以来就存在,该版本确实包含该规则,并且我没有发现有关需要添加该规则的缺陷报告。 )。

  

所以大概他们有一些想法,为什么编译器需要这种开销

我怀疑标准委员会没有考虑到任何特定的用例,而是添加了规则以使现有编译器保持这种行为。

  

MSVC可以将此开销用于什么目的? “为什么?”

只有MS编译器小组可以肯定地回答这些问题,但是我可以提出一些猜想:

该空间可由调试器使用,这将允许它显示数组的所有元素。地址清除程序可以使用它来验证阵列没有溢出。也就是说,我相信这两个工具都可以将数据存储在外部结构中。

考虑到开销仅在非平凡的析构函数的情况下才保留,可能是因为它用于存储到目前为止构造的元素数,以便编译器可以知道在发生异常时要销毁哪些元素在其中一个构造函数中。再次,据我所知,也可以将其存储在堆栈上的单独临时对象中。


对于它的价值,Itanium C ++ ABI同意不需要开销:

  

如果使用的new operator::operator new[](size_t, void*),则不需要cookie。

cookie 是指数组长度开销。

答案 1 :(得分:1)

动态数组分配是特定于实现的。但是实现动态数组分配的常见做法之一是在开始之前存储其大小(我的意思是在第一个元素之前存储大小)。这与以下内容完全重叠:

  

表示数组分配开销;结果   new-expression将从返回的值中减去此金额   通过运算符new []。

“删除展示位置”没有多大意义。 delete的作用是调用析构函数和可用内存。 delete在所有数组元素上调用析构函数并将其释放。在某种意义上,显式调用析构函数是“放置删除”。

答案 2 :(得分:0)

  

当前标准草案明确指出,放置new []可能会有空间开销...

是的,也打败了我。我将其(正确或错误)发布为一个问题在GitHub上,请参阅:

{{3}}

  

因此大概他们有一些想法,为什么编译器需要这种开销。它是什么?编译器可以将此开销用于任何有用的事情吗?

据我所知,不。

  

据我所知,要销毁此数组,唯一的解决方案是在循环中调用析构函数(我对吗?),因为没有放置删除[](顺便说一句,我们不应该有放置删除[ ]来正确地破坏数组,而不仅仅是数组的元素?)。因此,编译器不必知道数组长度。

绝对是您在此处所说的第一部分。但是我们不需要放置delete [](我们可以在循环中调用析构函数,因为我们知道有多少个元素)。

  

我认为,由于这种开销无法用于任何有用的东西,编译器不会使用它(因此,这实际上不是问题)。我已经用以下简单代码检查了编译器:

     

...

     

GCC和clang根本不使用任何开销。但是,MSVC在Foo情况下使用8个字节。 MSVC可以出于何种目的使用此开销?

令人沮丧。我确实虽然所有编译器都不会这样做,因为没有意义。它仅由delete []使用,并且无论如何您不能将其与位置new一起使用,所以...

因此,总而言之,放置new [ ]目的应该是让编译器知道数组中有多少个元素,以便它知道要调用多少个构造函数。这就是 all 。期。

答案 3 :(得分:-1)

(编辑:添加了更多详细信息)

  

但是此解决方案不会创建数组,但是使用operator []彼此相邻的元素(不是数组)对于它们来说是未定义的行为。

据我了解,事实并非如此。

  

[基本生活]
  类型为T的对象的生存期始于以下时间:
  (1.1)—获得了类型T合适的对齐方式和大小的存储,并且
  (1.2)—如果对象具有非空初始化,则其初始化完成

数组的初始化包括其元素的初始化。 (重要:该语句可能不受标准直接支持。如果确实不支持此语句,则这是标准中的一个缺陷,使得无法创建除new[]之外的可变长度数组。特别是用户无法为std::vector编写自己的替代文件。我不认为这是标准的意图。

因此,只要有一个char数组的大小和类型为N的{​​{1}}个对象的数组对齐,就满足第一个条件。

为了满足第二个条件,需要初始化T类型的N单个对象。可以通过一次将原始T数组地址递增char并在生成的指针上调用放置sizeof(T)来实现该初始化。