8
\$\begingroup\$

I'm having problems casting an array of uint8_t to a struct in C using a Silicon Labs MCU (EFM32GG11B120F2048GQ64). The byte array is the received data from a UART, and I want to cast it to a struct representing the message structure.

When casting the array to the struct, report_type (uint8_t) is byte [0] in the array, as it should. But start_time (uint32_t) is not the value of bytes [1..4] as I would think, but rather bytes [4..7]. Bytes [1..3] are not used.

I tried adding a second uint8_t member report_type2, effectively moving start_time down one position. report_type2 gets the value of byte [1], but start_time still gets the value of bytes [4..7].

Anybody knows what's going on here and why this is?

Byte array:

05 29 68 58 3C 0E 10 02 44 0D D5 C3 3F 0F 5C 29 00 05 69 75 00 00 0A B8 07 25 A7 11 00 B5 01 02 4C 1D 00 1C 0A FF FF 00 00 00 0A 01 B9 06 00 09 07 25 A7 11 00 B5 01 02 4C 1D 00 1C 0A FF FF 00 00 00 0A 01 B9 06 00 09 07 25 A7 11 00 B5 01 02 4C 1D 00 1C

Struct:

typedef struct
{
   uint8_t report_type;
   uint32_t start_time;
   uint16_t period_length;
   uint8_t status_alarm_code;
   float_union_t total;
   float_union_t average;
   subperiod_t subperiod_1;
   subperiod_t subperiod_2;
   subperiod_t subperiod_3;
   subperiod_t subperiod_4;
   subperiod_t subperiod_5;
   subperiod_t subperiod_6;
   subperiod_t subperiod_7;
   subperiod_t subperiod_8;
   subperiod_t subperiod_9;
   subperiod_t subperiod_10;
}report_t;

Casting array to struct:

report_t *report = (report_t *)packet->data;
\$\endgroup\$
2
  • 6
    \$\begingroup\$ Please read about struct padding, packing, and serialization. See this answer for starters. \$\endgroup\$
    – kkrambo
    Commented Apr 28, 2022 at 13:54
  • 4
    \$\begingroup\$ try typedef struct {...} __attribute__((packed)) report_t; \$\endgroup\$ Commented Apr 28, 2022 at 13:55

2 Answers 2

13
\$\begingroup\$

You cannot do dirty casts like this in C. There are multiple problems:

  • The data you copy from might not be aligned.
  • The struct might have padding bytes to compensate for internal alignment that are not present in the data you copy from.
  • Dereferencing this struct pointer after the cast leads to a strict pointer aliasing violation, meaning it's undefined behavior bug which could result in incorrect machine code getting generated.

The safest and most portabile solution is write a function that does serialization/deserialization of the struct data. That is, a function writing to each member manually. This is slightly slower due to the overhead code but the most portable.

Less portable but probably faster is to memcpy the data. If you do this, you must be absolutely sure that the struct has no padding. Which you aren't, the struct you posted is very poorly designed by someone who has never heard of alignment, since it has padding gaps all over it. If you re-design the struct to ensure there's no padding, you could use memcpy or similar methods.

(Union type punning is yet another option.)

\$\endgroup\$
13
  • 10
    \$\begingroup\$ As a rule of thumb, if you are a beginner to intermediately skilled C programmer, you should pretty much never use casts. This will save you from a ton of subtle bugs. When uncertain, ask on Stack Overflow. \$\endgroup\$
    – Lundin
    Commented Apr 28, 2022 at 13:56
  • 1
    \$\begingroup\$ This is Cortex-M. Any GCC based compiler will support packed structs on it. It might be a trick but it can be done as long as you know what you are doing. \$\endgroup\$
    – Justme
    Commented Apr 28, 2022 at 14:06
  • 1
    \$\begingroup\$ @Oystein Assuming you are using something like Silabs EFM then it's 32 bit aligned. A compiler is explicitly not allowed to re-order the memory of struct members, it's one of the few ways C provides that guarantees how data is stored. Therefore you end up with uint8_t report_type 3 byte padding then uint32_t start_time and so on. These padding bytes are nothing but waste of space. \$\endgroup\$
    – Lundin
    Commented Apr 28, 2022 at 14:11
  • 4
    \$\begingroup\$ @Oystein Check out this x86 example: godbolt.org/z/b1cM5TsYv. Without the "FIX" it's your original code, the struct always starts at an aligned address. Then report_type takes up 4 bytes instead of 1. #define FIX and it does nothing but reorder the members so that there will be no padding gaps. And suddenly the struct takes up 8 bytes instead of 12. \$\endgroup\$
    – Lundin
    Commented Apr 28, 2022 at 14:12
  • 2
    \$\begingroup\$ @Justme Or in this case just re-order the struct members correctly since their allocation order doesn't seem to matter. No need to disable padding. \$\endgroup\$
    – Lundin
    Commented Apr 28, 2022 at 14:13
8
\$\begingroup\$

Struct elements may not be assumed contiguous for data alignment reasons.

It seems your compiler assumes alignment to 32 bits.

Depending on your compiler, there might be a way to make it pack the struct elements, exact mehtod can be found in the compiler manual.

\$\endgroup\$
6
  • \$\begingroup\$ Packing isn't likely a viable solution. Data is aligned for a reason. \$\endgroup\$
    – Lundin
    Commented Apr 28, 2022 at 13:54
  • \$\begingroup\$ @Lundin Maybe. The compiler should be smart enough to read unaligned variables as long as it knows they're unaligned. \$\endgroup\$ Commented Apr 28, 2022 at 13:55
  • \$\begingroup\$ @user253751 Depends on the ISA. I'm not sure what "Silicon Labs MCU" means, probably either a Cortex M or some old 8051 crap in which cases it should be ok, but maybe they got other ISA too. \$\endgroup\$
    – Lundin
    Commented Apr 28, 2022 at 13:59
  • 1
    \$\begingroup\$ @Justme Thank you for the tip! While I'm sure you're correct, I think I will go for manually setting the struct members. Just to improve readability, if nothing else.. \$\endgroup\$
    – Oystein
    Commented Apr 28, 2022 at 14:20
  • 1
    \$\begingroup\$ Either way, disabling padding doesn't solve the strict aliasing violation. Don't write code with UB when it can be avoided. Especially not when gcc is the expected compiler, which I believe is the case at least in the disasterware known as "Siplicity Studio". \$\endgroup\$
    – Lundin
    Commented Apr 28, 2022 at 14:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.