C++ placement new after memset

Viewed 360

Suppose there's a struct whose constructor does not initialize all member variables:

struct Foo {
  int x;
  Foo() {}
}

If I memset some buffer to 0, use placement new on that buffer to create an instance of Foo, and then read x from that instance, is that defined behavior?

void bar(void* buf) {
  memset(buf, 0, sizeof(Foo));
  Foo* foo = new(buf) Foo;
  std::cout << foo.x; // Is this undefined behavior?
}
2 Answers

It is textbook undefined behavior. Member x is not initialized after the constructor, and reading uninitialized variable is undefined behavior.

The fact that this memory was previously filled with something else is irrelevant.

As a supplement to the other answer:

On the off chance that anyone feels like handwaving this away as "technically undefined behavior, but safe enough for me", allow me to demonstrate how thoroughly broken the resulting code can be.

If x is initialized:

struct Foo {
  int x = 0;
  Foo() {}
};

// slightly simpler bar()
int bar(void* buf) {
  std::memset(buf, 0, sizeof(Foo));
  Foo* foo = new(buf) Foo;
  return foo->x; 
}

g++-11 with -O3 produces the following:

bar(void*):
        mov     DWORD PTR [rdi], 0   <----- memset(buff, 0, 4) and/or int x = 0 
        xor     eax, eax             <----- Set the return value to 0
        ret

Which is just fine. In fact, it doesn't even exhibit whatever overhead one could hope to eliminate via in-place uninitialized construction. Compilers are smart.

In contrast to that, when leaving x uninitialized:

struct Foo {
  int x;
  Foo() {}
};
// ... same bar

We get, with the same compiler and settings:

bar(void*):
        mov     eax, DWORD PTR [rdi] <----- Just dereference buf as the result ?!?
        ret

Well, it's certainly faster, but what happened to the memset()?

The compiler figured that since we put an uninitialized int (aka junk) on top of the freshly memsetted memory, it doesn't even have to bother with the memset() in the first place. It can just "recycle" the junk that was there beforehand.

anything -> 0 -> anything collapses down to anything after all. So the function not altering the memory pointed at by buff is a reasonable interpretation of the code.

You can play around with these examples on godbolt here.

Related