Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

May placement `new` rely on underlying storage value?

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.

like image 314
Matthieu M. Avatar asked Jan 27 '17 09:01

Matthieu M.


People also ask

Does placement new allocate memory?

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.

Why use placement new?

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.

When to use placement new c++?

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`.


1 Answers

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.

like image 77
Barry Avatar answered Sep 24 '22 03:09

Barry