c++memory-managementmalloclanguage-lawyer

std::malloc() and starting lifetime of the object


Excerpt from the book "C++ memory management" by Patrice Roy

The std::memcpy() function

For historical (and C compatibility) reasons, std::memcpy() is special as it can start the lifetime of an object if used appropriately. An incorrect use of std::memcpy() for type punning would be as follows:

// suppose this holds for this example
static_assert(sizeof(int) == sizeof(float));
#include <cassert>
#include <cstdlib>
#include <cstring>
int main() {
   float f = 1.5f;
    void *p = malloc(sizeof f);
    assert(p);
    int *q = std::memcpy(p, &f, sizeof f);
    int value = *q; // UB
    //
}

The reason why this is illegal is that the call to std::memcpy() copies a float object into the storage pointed to by p, effectively starting the lifetime of a float object in that storage. Since q is an int*, dereferencing it is UB.

Is it really UB here ? Doesn't malloc() implicitly create an int (if sizeof(int) == sizeof(float), of course) and we can legally do memcpy to it and than read it value.


Solution

  • This is not UB.

    Presumably this is not controversial:

     float f = 1.5f;
     void *p = malloc(sizeof f);
     assert(p);
    

    This line becomes interesting:

    int *q = (int*)std::memcpy(p, &f, sizeof f);
    

    I've inserted the missing cast from void* to int*.

    std::memcpy implicitly creates objects in the destination region of storage before the copy and returns the new object: cstring.syn

    What objects does it create? Any objects of implicit lifetime types which give the program defined behavior. intro.object If there are many choices, it is unspecified which you get.

    Then consider:

     int value = *q; // Obviously NOT UB
    

    This is defined so long as the object created was an int, so it must have been.