Can the `new` placement rely on the underlying storage value?

Let's start with some context.

The user memory pool used 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 constructor can assume that the store is already initialized to zero and therefore only initializes those members that require a different value.

GCC (6.2 and 6.3 at least), however, has an β€œinteresting” optimization that works.

 struct Memset { Memset(FastInitialization) { memset(this, 0, sizeof(Memset)); } double mDouble; unsigned mUnsigned; }; Memset* make_memset() { return create<Memset>(); } 

Compiles 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 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 corresponding to its base are initialized. Debugging information confirms that memset(ptr, 0, sizeof(T)); completely removed.

On the other hand, both ICC and Clang both call memset in full size, here is the result of 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 

Thus, the behavior of GCC and Clang is different, and the question arises: is GCC correct and creates a better assembly, or is Clang right and GCC buggy?


Or, in terms of language advocacy:

In what circumstances can a constructor rely on a previous value stored in allocated memory?

Note. I guess this only matters when placing new , but I'm happy it will be shown differently.

+8
c ++ optimization language-lawyer
source share
2 answers

Can a new placement rely on the underlying storage value?

No, he can not. From [dcl.init]:

If no initializer is specified for the object, the object is initialized by default. When storing an object with automatic or dynamic storage duration, the object has an undefined value, and if the object is not initialized, this object retains an undefined value until this value is replaced (5.18).

An indefinite value means precisely this, an indefinite. This does not mean that if a new expression is placed, the previous memory is necessarily preserved. The compiler is allowed to do whatever it wants in memory, which includes, but is not limited to, nothing.

In what circumstances can a constructor rely on a previous value stored in allocated memory?

The following paragraph in [dcl.init] lists cases where the behavior is not undefined when creating an undefined value, but they only apply to unsigned strings.

So, under no circumstances.

+10
source share

Both GCC and Clang are correct - leaving () the native DerivedMemset data DerivedMemset uninitialized will result in undefined behavior as soon as their values ​​are available. Therefore, the compiler is given an exit license - right before the constructor is called - any bit pattern in the storage area that will be occupied by these fields.

+1
source share

All Articles