让我们从一些背景开始。
自定义内存池使用的代码类似于以下代码:
struct FastInitialization {};
template <typename T>
T* create() {
static FastInitialization const F = {};
void* ptr = malloc(sizeof(T));
memset(ptr, 0, sizeof(T));
new (ptr) T(F);
return reinterpret_cast<T*>(ptr);
}
这个想法是,当使用FastInitialization
调用时,构造函数可以假设存储已经零初始化,因此只初始化那些需要不同值的成员。
struct Memset {
Memset(FastInitialization) { memset(this, 0, sizeof(Memset)); }
double mDouble;
unsigned mUnsigned;
};
Memset* make_memset() {
return create<Memset>();
}
编译为:
make_memset():
sub rsp, 8
mov edi, 16
call malloc
mov QWORD PTR [rax], 0
mov QWORD PTR [rax+8], 0
add rsp, 8
ret
可是:
struct DerivedMemset: Memset {
DerivedMemset(FastInitialization f): Memset(f) {}
double mOther;
double mYam;
};
DerivedMemset* make_derived_memset() {
return create<DerivedMemset>();
}
编译为:
make_derived_memset():
sub rsp, 8
mov edi, 32
call malloc
mov QWORD PTR [rax], 0
mov QWORD PTR [rax+8], 0
add rsp, 8
ret
也就是说,只有struct
的前16个字节(与其基数对应的部分)已经初始化。调试信息确认已完全取消对memset(ptr, 0, sizeof(T));
的调用。
另一方面,ICC和Clang都在全尺寸上调用memset
,这是Clang的结果:
make_derived_memset(): # @make_derived_memset()
push rax
mov edi, 32
call malloc
xorps xmm0, xmm0
movups xmmword ptr [rax + 16], xmm0
movups xmmword ptr [rax], xmm0
pop rcx
ret
因此GCC和Clang的行为有所不同,问题就变成了:GCC是否正确并且产生了更好的装配,或者是Clang是否正确以及GCC错误?
或者,就语言律师而言:
构造函数在哪种情况下可以依赖存储在其分配的存储中的先前值?
注意:我认为这只适用于展示位置new
,但我很高兴以其他方式展示。
答案 0 :(得分:11)
五月展示位置
new
是否依赖底层存储空间值?
不,可能不会。来自[dcl.init]:
如果没有为对象指定初始值设定项,则默认初始化该对象。存储对象时 如果获得自动或动态存储持续时间,则该对象具有不确定的值,如果未对该对象执行初始化,则该对象将保留不确定的值,直到该值被替换为止(5.18)。
不确定价值意味着不确定。这并不意味着,在放置新表达式的情况下,必须保持先前的存储器。允许编译器对内存执行任何操作 - 包括但不限于任何内容。
在哪种情况下构造函数可能依赖于存储在其分配的存储中的先前值?
[dcl.init]中的后续段落列出了在生成不确定值时行为不未定义的情况,但它们只与无符号窄字符类型有关。
所以,在任何情况下都没有。
答案 1 :(得分:1)
GCC和Clang都是正确的 - 保留DerivedMemset
未经初始化的(自己的)数据成员将在访问其值时立即导致未定义的行为。因此,在调用构造函数之前,编译器将获得离开的许可 - 存储范围内将被这些字段占用的任何位模式。