放置新[]的开销
Overhead of placement new[]
当前的标准草案明确规定放置new[]
可能有空间开销:
此开销可能应用于所有数组new表达式,包括那些引用库函数运算符new[](std::size_t、void*)和其他位置分配函数。每次调用new时,开销的大小可能会有所不同。
所以他们可能有一些想法,为什么编译器需要这种开销。它是什么?编译器能把这个开销用于任何有用的事情吗?
在我的理解中,要破坏这个数组,唯一的解决方案是在循环中调用析构函数(我说得对吗?),因为没有放置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[]: %dn", (int)(reinterpret_cast<char*>(fl) - buffer1));
printf("overhead for Foo[] : %dn", (int)(reinterpret_cast<char*>(foo) - buffer2));
}
GCC和clang根本不使用任何开销。但是,MSVC在Foo
的情况下使用8个字节。MSVC将这笔开销用于什么目的?
以下是我提出这个问题的一些背景。
之前有关于这个主题的问题:
- 新的数组放置是否需要缓冲区中的未指定开销
- 阵列的新放置可以以可移植的方式使用吗
在我看来,这些问题的寓意是避免使用放置new[]
,并在循环中使用放置new
。但该解决方案不创建数组,而是相邻的元素,这是而不是数组,使用operator[]
对它们来说是未定义的行为。这些问题更多地是关于如何避免放置new[]
,但这个问题更多的是关于"为什么?"。
当前标准草案明确规定。。。
为了澄清,该规则(可能)自标准的第一个版本以来就已经存在(我可以访问的最早版本是C++03,它确实包含该规则,我没有发现需要添加该规则的缺陷报告)。
所以他们可能有一些想法,为什么编译器需要这个开销
我的怀疑是,标准委员会没有考虑任何特定的用例,但添加了规则,以保持现有编译器符合这种行为。
MSVC将此开销用于什么目的?"为什么?">
只有MS编译器团队才能自信地回答这些问题,但我可以提出一些猜测:
调试器可以使用该空间,这样可以显示数组的所有元素。地址消毒剂可以使用它来验证数组是否溢出。也就是说,我相信这两种工具都可以将数据存储在外部结构中。
考虑到开销仅在非平凡析构函数的情况下保留,它可能用于存储迄今为止构造的元素数量,以便编译器可以知道在其中一个构造函数中发生异常时要销毁哪些元素。同样,据我所知,这也可以存储在堆栈上的一个单独的临时对象中。
就其价值而言,安腾C++ABI同意不需要开销:
如果使用的
new operator
是::operator new[](size_t, void*)
,则不需要cookie。
其中cookie表示数组长度开销。
动态数组分配是特定于实现的。但实现动态数组分配的常见做法之一是在开始之前存储其大小(我的意思是在第一个元素之前存储大小)。这与完全重叠
表示数组分配开销;的结果新表达式将从返回的值中偏移这个量操作员new[]。
"Placement delete"没有多大意义。delete
的作用是调用析构函数并释放内存。delete
调用所有数组元素的析构函数并释放它。显式调用析构函数在某种意义上是"placement-delete"。
当前的标准草案明确指出placement-new[]可能会有空间开销。。。
是的,我也快累死了。我把它作为一个问题(正确或错误)发布在GitHub上,请参阅:
https://github.com/cplusplus/draft/issues/2264
所以他们可能有一些想法,为什么编译器需要这种开销。它是什么?编译器能把这个开销用于任何有用的事情吗?
据我所见,没有。
在我的理解中,要破坏这个数组,唯一的解决方案是在循环中调用析构函数(我说得对吗。因此编译器不必知道数组的长度。
对于你在那里说的第一部分,绝对。但我们不需要放置delete []
(我们可以在循环中调用析构函数,因为我们知道有多少元素)。
我认为由于这个开销不能用于任何有用的事情,编译器不会使用它(所以这在实践中不是问题)。我已经检查了使用以下简单代码的编译器:
GCC和clang根本不使用任何开销。但是,MSVC在Foo情况下使用了8个字节。MSVC将这笔开销用于什么目的?
这太令人沮丧了。我真的认为所有编译器都不会这么做,因为这毫无意义。它只由delete []
使用,无论如何都不能与放置new
一起使用,所以…
总之,放置new [ ]
的目的应该是让编译器知道数组中有多少元素,这样它就知道要调用多少构造函数。这就是它应该做的一切。
(编辑:添加更多细节)
但是这个解决方案不创建数组,但是相邻的元素不是数组,使用运算符[]对它们来说是未定义的行为。
据我所知,这并不完全正确。
[basic.life]
类型T的对象的生存期开始于:
(1.1)-获得具有适当对齐和大小的类型T的存储,
数组的初始化包括其元素的初始化。(重要提示:标准可能不直接支持此语句。如果确实不支持,则这是标准中的一个缺陷,导致未定义new[]
以外的可变长度数组的创建。特别是,用户无法编写自己的std::vector
替代品。我不认为这是标准的意图)。
因此,每当对于类型为T
的N
对象的阵列存在适当大小和对齐的char
阵列时,就满足第一个条件。
为了满足第二个条件,需要初始化类型为T
的N
单个对象。这种初始化可以通过一次将原始char
阵列地址增加sizeof(T)
,并在得到的指针上调用放置new
来实现。
- 实现无开销push_back的最佳方法是什么
- 别名模板的专业化 C++11 中没有开销的最佳替代方案
- C++标准是否允许<double>在没有开销的情况下实现 std::可选
- 类型擦除的std::function与虚拟函数调用的开销
- 一组值的零开销下标运算符
- C++ 特征库:引用的性能开销<>
- C++对开销较少的容器使用多个过滤器
- 在编译时评估函数开销的通用方法
- 在循环中调用同一虚函数的开销
- 使用静态成员函数而不是普通函数是否有任何开销?
- 自定义运算符重载C++,无开销
- 在 v8 JavaScript 中重复调用C++是否有巨大的开销?
- 将 mmap 内存用于开销非常低的循环缓冲区
- 与纯 V8 相比,NodeJS 是否有任何性能缺陷或显著开销?
- 非 constexpr 变量模板的开销是否为零?
- 右值引用是否具有与右值引用相同的开销?
- 实例成员与静态成员与非类方法的开销
- 如果使用lambda,std::unique_ptr如何没有大小开销
- 放置新[]的开销
- 这种获取模板参数包中最后一个元素的方法是否有隐藏的开销?