Is reusing a memory location safe?

Solution 1:

It's "OK", it works as you have written it (assuming primitives and plain-old-datatypes (PODs)). It is safe. It is effectively a custom memory manager.

Some notes:

  • If objects with non-trivial destructors are created in the location of the allocated memory, make sure it is called

    obj->~obj();
    
  • If creating objects, consider the placement new syntax over a plain cast (works with PODs as well)

    Object* obj = new (data) Object();
    
  • Check for a nullptr (or NULL), if malloc fails, NULL is returned

  • Alignment shouldn't a problem, but always be aware of it when creating a memory manager and make sure that the alignment is appropriate

Given you are using a C++ compiler, unless you want to keep the "C" nature to the code you can also look to the global operator new().

And as always, once done don't forget the free() (or delete if using new)


You mention that you are not going to convert any of the code just yet; but if or when you do consider it, there are a few idiomatic features in C++ you may wish to use over the malloc or even the global ::operator new.

You should look to the smart pointer std::unique_ptr<> or std::shared_ptr<> and allow them to take care of the memory management issues.

Solution 2:

Depending on the definition of Data, your code might be broken. It's bad code, either way.

If Data is a plain old data type (POD, i.e. a typedef for a basic type, a struct of POD types etc.), and the allocated memory is properly aligned for the type (*), then your code is well-defined, which means it will "work" (as long as you initialize each member of *data_d before using it), but it is not good practice. (See below.)

If Data is a non-POD type, you are heading for trouble: The pointer assignment would not have invoked any constructors, for example. data_d, which is of type "pointer to Data", would effectively be lying because it points at something, but that something is not of type Data because no such type has been created / constructed / initialized. Undefined behaviour will be not far off at that point.

The solution for properly constructing an object at a given memory location is called placement new:

Data * data_d = new (data) Data();

This instructs the compiler to construct a Data object at the location data. This will work for POD and non-POD types alike. You will also need to call the destructor (data_d->~Data()) to make sure it is run before deleteing the memory.

Take good care to never mix the allocation / release functions. Whatever you malloc() needs to be free()d, what is allocated with new needs delete, and if you new [] you have to delete []. Any other combination is UB.


In any case, using "naked" pointers for memory ownership is discouraged in C++. You should either

  1. put new in a constructor and the corresponding delete in the destructor of a class, making the object the owner of the memory (including proper deallocation when the object goes out of scope, e.g. in the case of an exception); or

  2. use a smart pointer which effectively does the above for you.


(*): Implementations are known to define "extended" types, the alignment requirements of which are not taken into account by malloc(). I'm not sure if language lawyers would still call them "POD", actually. MSVC, for example, does 8-byte alignment on malloc() but defines the SSE extended type __m128 as having a 16-byte alignment requirement.

Solution 3:

The rules surrounding strict aliasing can be quite tricky.

An example of strict aliasing is:

int a = 0;
float* f = reinterpret_cast<float*>(&a);
f = 0.3;
printf("%d", a);

This is a strict aliasing violation because:

  • the lifetime of the variables (and their use) overlap
  • they are interpreting the same piece of memory through two different "lenses"

If you are not doing both at the same time, then your code does not violate strict aliasing.


In C++, the lifetime of an object starts when the constructor ends and stops when the destructor starts.

In the case of built-in types (no destructor) or PODs (trivial destructor), the rule is instead that their lifetime ends whenever the memory is either overwritten or freed.

Note: this is specifically to support writing memory managers; after all malloc is written in C and operator new is written in C++ and they are explicitly allowed to pool memory.


I specifically used lenses instead of types because the rule is a bit more difficult.

C++ generally use nominal typing: if two types have a different name, they are different. If you access a value of dynamic type T as if it were a U, then you are violating aliasing.

There are a number of exceptions to this rule:

  • access by base class
  • in PODs, access as a pointer to the first attribute

And the most complicated rule is related to union where C++ shifts to structural typing: you can access a piece of memory through two different types, if you only access parts at the beginning of this piece of memory in which the two types share a common initial sequence.

§9.2/18 If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them. Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members.

Given:

  • struct A { int a; };
  • struct B: A { char c; double d; };
  • struct C { int a; char c; char* z; };

Within a union X { B b; C c; }; you can access x.b.a, x.b.c and x.c.a, x.c.c at the same time; however accessing x.b.d (respectively x.c.z) is a violation of aliasing if the currently stored type is not B (respectively not C).

Note: informally, structural typing is like mapping down the type to a tuple of its fields (flattening them).

Note: char* is specifically exempt from this rule, you can view any piece of memory through char*.


In your case, without the definition of Data I cannot say whether the "lenses" rule could be violated, however since you are:

  • overwriting memory with Data before accessing it through Data*
  • not accessing it through int* afterwards

then you are compliant with the lifetime rule, and thus there is no aliasing taking place as far as the language is concerned.

Solution 4:

As long as the memory is used for only one thing at a time it's safe. You're basically use the allocated data as a union.

If you want to use the memory for instances of classes and not only simple C-style structures or data-types, you have to remember to do placement new to "allocate" the objects, as this will actually call the constructor of the object. The destructor you have to call explicitly when you're done with the object, you can't delete it.

Solution 5:

As long as you only handle "C"-types, this would be ok. But as soon as you use C++ classes you will get into trouble with proper initialization. If we assume that Data would be std::string for example, the code would be very wrong.

The compiler cannot really move the store across the call to printf, because that is a visible side effect. The result has to be as if the side effects are produced in the order the program prescribes.