Search code examples
c++undefined-behaviormemsetplacement-new

C++ placement new after memset


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?
}

Solution

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