In constrained embedded environments, I prefer to overlay packed structs directly over the data (either by pointer cast or union type punning), allowing one single header file, which can be included from both sides, define the whole protocol, so that there is no need to write any data generation or parsing code anywhere; just set the variables, send the struct, receive struct, access variables.
That's very simple but definitely makes perfect sense. I can't believe I didn't think of that! I'm a team of one (me) with nobody peering over my shoulder, and I am comfortable with that implementation, so I will probably go with that.
Just remember, assuming GCC
* If you do it by pointer casting and not unions, use -fno-strict-aliasing to prevent compiler doing stupid assumptions
* Use stdint.h types like uint32_t, not "int".
* __attribute__((packed)), so that the structs are indeed the same on both sides
* __attribute__((aligned(8))), make sure each of the structs are aligned in memory, for example if you have a buffer containing the data.
* If alignment is impossible to define beforehand, for example you receive arbitrary number of bytes in a buffer and need to parse something at an arbitrary position,
memcpy first. While there is a performance penalty, is still at least as efficient, or more efficient, than parsing it together with arithmetic, and it's still just one line of code. But best to avoid such situation. (I'm assuming ARM here. A pure x86 solution would work fine with unaligned access, just slower.)
Most of the networking code out there uses this pattern, and for example the TCP header fields have been
designed from scratch alignment in mind! Do the same, for example
struct PACK
{
uint8_t status;
uint8_t reserved_for_future_use;
int16_t temperature;
uint32_t counter;
}
instead of
struct PACK
{
uint8_t status;
uint32_t counter;
uint8_t reserved_for_future_use;
int16_t temperature;
}
In the former, everything is aligned as long as the struct itself is aligned per the requirements of the longest type (8 pretty much always works, but 4 would work here). In the latter, compiler would want to add padding, which is prevented, and you have unaligned access for counter and temperature.
Yeah, a few things to remember and take care of.