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.
The strict aliasing rule dictates that pointers are assumed not to alias if they point to fundamentally different types, except for char* and void* which can alias to any other data type.
In both C and C++ the standard specifies which expression types are allowed to alias which types. The compiler and optimizer are allowed to assume we follow the aliasing rules strictly, hence the term strict aliasing rule.
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 presence 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With