Reputation: 299800
Let's start with some context.
A custom memory pool was using code similar to the following:
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);
}
The idea is that when called with FastInitialization
, a constructor could assume that the storage is already zero-initialized and therefore only initialize those members who need a different value.
GCC (6.2 and 6.3, at least) however has an "interesting" optimization which kicks in.
struct Memset {
Memset(FastInitialization) { memset(this, 0, sizeof(Memset)); }
double mDouble;
unsigned mUnsigned;
};
Memset* make_memset() {
return create<Memset>();
}
Compiles down to:
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
But:
struct DerivedMemset: Memset {
DerivedMemset(FastInitialization f): Memset(f) {}
double mOther;
double mYam;
};
DerivedMemset* make_derived_memset() {
return create<DerivedMemset>();
}
Compiles down to:
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
That is, only the first 16 bytes of the struct
, the part corresponding to its base, have been initialized. Debugging information confirm that the call to memset(ptr, 0, sizeof(T));
has been completely elided.
On the other hand, both ICC and Clang both call memset
on the full size, here is Clang's result:
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
So the behavior of GCC and Clang differ, and the question becomes: is GCC right and producing better assembly, or is Clang right and GCC buggy?
Or, in terms of language lawyering:
Under which circumstances may a constructor rely on the previous value stored in its allocated storage?
Note: I assume this only matters with placement new
, but I am happy to be shown otherwise.
Upvotes: 8
Views: 1073
Reputation: 302852
May placement
new
rely on underlying storage value?
No, it may not. From [dcl.init]:
If no initializer is specified for an object, the object is default-initialized. When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced (5.18).
Indeterminate value means just that, indeterminate. It does not mean that, in the case of a placement new expression, the previous memory is necessarily maintained. The compiler is allowed to do whatever it wants to the memory - which includes, but is not limited to, nothing.
Under which circumstances may a constructor rely on the previous value stored in its allocated storage?
The subsequent paragraph in [dcl.init] lists cases where the behavior is not undefined when producing an indeterminate value, but they only have to do with unsigned narrow character types.
So, under no circumstances.
Upvotes: 11
Reputation: 32474
Both GCC and Clang are correct - leaving the (own) data members of DerivedMemset
uninitialized will result in undefined behavior as soon as their values are accessed. Therefore the compiler is given the license to leave - right before the constructor is invoked - any bit pattern at the storage range that will be occupied by those fields.
Upvotes: 1