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.
Placement new is a variation new operator in C++. Normal new operator does two things : (1) Allocates memory (2) Constructs an object in allocated memory. Placement new allows us to separate above two things. In placement new, we can pass a preallocated memory and construct an object in the passed memory.
Placement new allows you to construct an object in memory that's already allocated. You may want to do this for optimization when you need to construct multiple instances of an object, and it is faster not to re-allocate memory each time you need a new instance.
Placement new This is used to construct objects in allocated storage: // within any block scope... { // Statically allocate the storage with automatic storage duration // which is large enough for any object of type `T`.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With