MISRA C:2012 11.3 violation casting (float *) to (uint32_t *)

83 Views Asked by At

I have the following code from a serial message encoder:

uint32_t bits;
*((float *) &bits) = some_external_variable;
pFrame->data[0x2C] = ((uint8_t)(bits >> 0x18) & 0xFFu);
pFrame->data[0x2D] = ((uint8_t)(bits >> 0x10) & 0xFFu);
pFrame->data[0x2E] = ((uint8_t)(bits >> 0x08) & 0xFFu);
pFrame->data[0x2F] = ((uint8_t)(bits >> 0x00) & 0xFFu);

I am trying to put the float value onto the bytestream in an endianness independent way. To do that, I (think I) need to access the bytes and put them onto the stream individually.

The byte mapping itself appears MISRA-C compliant, but I cannot figure out any way to serialize the float in a compliant way without triggering rule 11.3 using the cppcheck tool.

How could I encode the float in a compliant way?

Both data types should have the same size, so alignment should not be an issue here?

UPDATE:

Thanks for the answers and especially the clarification for the hidden pitfalls. I went with this solution in the end:

uint32_t bits;
(void)memcpy(&bits, some_external_variable, sizeof(bits));
pFrame->data[0x2C] |= ((uint8_t)(bits >> 0x00) & 0xFFu);
pFrame->data[0x2D] |= ((uint8_t)(bits >> 0x08) & 0xFFu);
pFrame->data[0x2E] |= ((uint8_t)(bits >> 0x10) & 0xFFu);
pFrame->data[0x2F] |= ((uint8_t)(bits >> 0x18) & 0xFFu);

Disclaimer: this code is the output of my code generator, and from that context I know in advance that the sizes of the variables match. The extensive shifts and masks look like no-op here, but there are cases with misaligned containers where the masks will play a role.

2

There are 2 best solutions below

1
dbush On BEST ANSWER

Casting a pointer to one type to a pointer to different type and dereferencing (except in a few special cases) is undefined behavior.

One case where it is well defined is if you cast to a character type which lets you access the bytes of the variable's representation:

unsigned char *bits = (unsigned char *)some_external_variable;
pFrame->data[0x2C] = bits[3];
pFrame->data[0x2D] = bits[2];
pFrame->data[0x2E] = bits[1];
pFrame->data[0x2F] = bits[0];

Or you can just use memcpy:

memcpy(&pFrame->data[0x2C], some_external_variable, 
       sizeof some_external_variable);

As you mentioned endian-independent, you can first memcpy to uint32_t, then do the shifts as you were before:

uint32_t bits;
memcpy(&bits, some_external_variable, sizeof bits);
pFrame->data[0x2C] = ((uint8_t)(bits >> 0x18) & 0xFFu);
pFrame->data[0x2D] = ((uint8_t)(bits >> 0x10) & 0xFFu);
pFrame->data[0x2E] = ((uint8_t)(bits >> 0x08) & 0xFFu);
pFrame->data[0x2F] = ((uint8_t)(bits >> 0x00) & 0xFFu);

This assumes that a float and a uint32_t use the same endianness, although I'm not aware of any implementation where they might be different.

0
Lundin On

but I cannot figure out any way to serialize the float in a compliant way without triggering rule 11.3

The rationale of 11.3 is lacking. It says that you may not do wild and crazy pointer conversions such as the dirty *((float *) &bits), because it may lead to misalignment, which in turn leads to undefined behavior. What it fails to mention is that even if the types are of the same alignment, you still have undefined behavior upon de-referencing due to strict aliasing. So you shouldn't be doing this regardless of alignment and MISRA-C won't allow it either.

Furthermore, MISRA-C allows one exception to rule 11.3 and that is when converting pointer-to-object into pointer-to-character-type (but not the other way around). This is an exception to strict aliasing in the C standard too: we may inspect any object byte by byte by accessing it through a character type pointer as if it was an array of character type.

So this is MISRA compliant and well-defined:

float some_external_variable = 3.14f;
unsigned char* ptr = (unsigned char*)&some_external_variable;

pFrame->data[0x2Cu] = ptr[3u];
pFrame->data[0x2Du] = ptr[2u];
pFrame->data[0x2Eu] = ptr[1u];
pFrame->data[0x2Fu] = ptr[0u];

(Please note that as soon as the intention is to use an unsigned integer constant - which most often is the case - we must add the u or U suffix for MISRA compliance, and to keep the static analyzer quiet.)

However, the above is non-portable since it assumes little endian. We could re-write it in a portable way with a few macros, which is sketchy for MISRA compliance, but doable.

A macro to determine endianess might look like this:

#define BIG_ENDIAN ( (*(unsigned char*)&(uint16_t){1u}) == 1u )

This will give a compile-time integer constant expression. And as far as MISRA is concerned, this does not violate 11.3 since we use character type. All operands are essentially unsigned. The result of the macro is essentially boolean since it uses ==. And we can't really use a function in place of the macro since we need the macro to result in an integer constant expression.

We can use this to cook up another macro subtracting an offset in case of big endian:

#define ENDIAN_INDEX(type,n) (BIG_ENDIAN ? (sizeof(type) -1u - (n)) : 0u)

Usage: ENDIAN_INDEX(float, 3) or ENDIAN_INDEX(some_external_variable, 3) which will expand to 0 in case of big endian, otherwise 3.

Example:

#define BIG_ENDIAN ( (*(uint8_t*)&(uint16_t){1u}) == 1u )
#define ENDIAN_INDEX(type,n) (BIG_ENDIAN ? (sizeof(type) -1u - (n)) : 0u)

...

unsigned char* ptr = (unsigned char*)&some_external_variable;

pFrame->data[0x2Cu] = ptr[ENDIAN_INDEX(float,3u)];
pFrame->data[0x2Du] = ptr[ENDIAN_INDEX(float,2u)];
pFrame->data[0x2Eu] = ptr[ENDIAN_INDEX(float,1u)];
pFrame->data[0x2Fu] = ptr[ENDIAN_INDEX(float,0u)];

This is well-defined and fully portable code.

It is indeed a little sketchy for MISRA compliance, probably breaking some advisory rule I can't remember on top of my head. You could of course get rid of the macros and type it all out in one big, unholy one-liner... but that won't help readability.