Strict aliasing rule uint8_t buffer to structure

  • A+
Category:Languages

Consider I have a typedef with bit fields as below.

typedef struct VIN_oCAN01_3abd61be {    uint64_t var1:24;    uint64_t var2:4;    uint64_t var3:4 }__attribute__((packed))Message; 

And I receive uint8_t buffer as below.

uint8_t buffer[4] = {0x1,0x2,0x3,0x14}; 

Currently in my production program my team is suggesting below approach.

Message *ptrMsg = (Message *)buffer; 

That is, assigning uint8_t buffer to Message type pointer. I had suggested that proposed approach does not follow strict aliasing rule and instead we should do as below.

Message msg; memcpy(&msg,buffer, sizeof(msg)); 

Note there is no option of copying the buffer to structure manually(member by member) as structure is very big.

Is my understanding is correct? If so can you please provide the standard doc which I can use to prove my point.

 


I had suggested that proposed approach does not follow strict aliasing rule

Correct. ptrMsg = (Message *)buffer means that you cannot access the data of ptrMsg without invoking undefined behavior.

You can prove your point with C17 6.5 §7 (cited here - What is the strict aliasing rule?). An lvalue expression such as ptrMsg->var1 = value does not access the stored value through a type compatible with the effective type of what's stored there, nor through any of the allowed expressions.

You can however go from Message to an array of uint8_t (assuming uint8_t is a character type) without violating strict aliasing.


The larger problem is however the precece of the bit-field in the first place, which is non-standard and non-portable. For example, you cannot know which part of the bit-field that is the MSB and LSB. You cannot know how the bits are aligned in the 64 bit type. Using a 64 bit type for a bit-field is a non-standard extension. It is endianess-dependent. And so on.


Assuming the 24 bits refer to bit 31 to 8 (we can't know by reading your code), then proper code without strict aliasing violations, bit-field madness and non-standard "struct padding killers" would look like this:

typedef union {    uint32_t var;    uint8_t  bytes[4]; } Message;   uint8_t buffer[4]; Message* ptrMsg = (Message*)buffer; uint32_t var1 = (ptrMsg->var >> 8); uint8_t  var2 = (ptrMsg->var >> 4) & 0x0F; uint8_t  var3 = (ptrMsg->var) & 0x0F; 

Message being "a union type that includes one of the aforementioned types among its members". Meaning it contains a type compatible with uint8_t [4].

This code also contains no copying and the shifts will get translated to the relevant bit access in the machine code.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: