vela18
vela18

Reputation: 37

C++ strict aliasing violation

I have a (char*) buffer. Am I allowed to do as follows:

  1. int8_t i8 = *(int8_t*)buffer;

  2. int16_t i16 = *(int16_t*)buffer;

  3. float f = *(float*)buffer;

  4. int8_t i = 22; *(int8_t*)buffer = i

  5. int16_t i = 25; *(int16_t*)buffer = i

  6. float f = 16.4; *(float*)buffer = f

EDIT: Let's assume the buffer was created in one of these ways:

  1. char* buffer = new char[300 * sizeof(char)];
  2. char* buffer = (char*)malloc(300 * sizeof(char));

Upvotes: 1

Views: 171

Answers (1)

user17732522
user17732522

Reputation: 76859

For the version with new neither of them is guaranteed to not have undefined behavior on all possible implementations:

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

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

  3. is guaranteed to be an aliasing violation.

  4. same as 1. minus the indeterminate value issue.

  5. same as 2. minus the indeterminate value issue.

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

Related Questions