Reputation: 37
I have a (char*) buffer. Am I allowed to do as follows:
int8_t i8 = *(int8_t*)buffer;
int16_t i16 = *(int16_t*)buffer;
float f = *(float*)buffer;
int8_t i = 22; *(int8_t*)buffer = i
int16_t i = 25; *(int16_t*)buffer = i
float f = 16.4; *(float*)buffer = f
EDIT: Let's assume the buffer was created in one of these ways:
char* buffer = new char[300 * sizeof(char)];
char* buffer = (char*)malloc(300 * sizeof(char));
Upvotes: 1
Views: 171
Reputation: 76859
For the version with new
neither of them is guaranteed to not have undefined behavior on all possible implementations:
is an aliasing violation (and therefore undefined behavior) if int8_t
is not char
or signed char
(which it however is on all platforms I am aware of). It is also reading an indeterminate value if you don't assign a value to buffer[0]
inbetween, also causing undefined behavior.
is also an aliasing violation if int16_t
is not char
or signed char
, which it can't be on any usual platform with CHAR_BIT == 8
. Initialization of buffer[0]
is missing again.
is guaranteed to be an aliasing violation.
same as 1. minus the indeterminate value issue.
same as 2. minus the indeterminate value issue.
same as 3.
For the version with malloc
the following holds, assuming you only ever access buffer
with only one out of the types you list:
4., 5., and 6. are all allowed assuming the sizeof
of the pointed-to type is at most 300
.
1., 2. and 3.: These are allowed under the condition above and assuming you actually write to the buffer in the way that you do in 4., 5. or 6. with matching type first.
Note that you can remedy the issues in the new
case with the help of std::launder
to make it equivalent to the malloc
case.
Also note that the malloc
case only works because malloc
is specified to implicitly create object of implicit lifetime type and return a pointer to a suitable one of these created objects. And all scalar types are implicit lifetime types, meaning all the ones you listed are as well.
It would be much easier not to make a mistake here by using placement-new to create the object(s) you want to store in the buffer explicitly. The placement-new returns a pointer to the newly created object that you can use without any casting.
None of this in any way allows writing to the buffer with one type and then reading with another or writing with different types to the same buffer location. That is always going to be an aliasing violation one way or another if no placement-new or similar intervenes to change the type (and reinitialize the value!). For casting between bit representations of different types use std::bit_cast
instead. (And maybe std::start_lifetime_as
in C++23 is also helpful.)
Upvotes: 2