Search code examples
c++cunionsstrict-aliasingtype-punning

free/delete union malloc/new Array in C/C++


I was working and was considering using a union. I decided against it, because the design really called for a struct/class, but it eventually lead to the following hypothetical question:

Suppose you have a union like this contrived example:

typedef union {
    char* array_c;
    float* array_f;
    int* array_i;
} my_array;

. . . and then you allocate one of the arrays and try deleting it from somewhere else:

my_array arr;
arr.array_f = (float*)(malloc(10*sizeof(float)));
free(arr.array_i);

I assume that this would work, although it is technically not defined, because of the way malloc is implemented. I also assume it would work when allocating array_c, even though, unlike int vs. float, the arrays are unlikely to be the same size.

The test could be repeated with new and delete, which are similar. I conjecture these would also work.

I'm guessing that the language specifications would hate me for doing this, but I would expect it would work. It reminds me of the "don't delete a new-ed pointer cast to void* even when it's an array not an object" business.

So questions: what does the specification say about doing this? I checked briefly, but couldn't find anything that addresses this case in particular. How ill-advised is this anyway--from a functional perspective (I realize that this is terrible from a clarity perspective).

This is purely a curiosity question for pedantic purposes.


Solution

  • You're precisely correct. It breaks the rules:

    When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values, but the value of the union object shall not thereby become a trap representation.
        - ISO/IEC standard 9899, section 6.2.6.1

    However, the way implementations are typically done, it will "accidentally" work properly. Since free takes a void *, the parameter will be converted to a void * to pass to free. Since all the pointers are located at the same address and all the conversions to a void * involve no change to their value, the ultimate value passed to free will be the same as if the correct member was passed.

    Theoretically, an implementation could track which member of a union was accessed last and corrupt the value (or crash the program, or do anything else) if you read a different member from the one you last wrote. But to my knowledge, no actual implementation does anything like that.