Flexible array members can only be placed at the end of the struct. That's just how the C standard 6.7.2.1 defines them:
As a special case, the last element of a structure with more than one named member may
have an incomplete array type; this is called a flexible array member.
But for the specific case they are also the wrong solution to the wrong question. The wrong question being "how do I store a variable size Modbus data protocol frame inside a C struct"? struct
is often better to avoid in the first place. Us C programmers have unfortunately been pretty much brainwashed to use struct
in every single situation, to the point where we just declare one without second thought.
There's various problems with structs, most notably the alignment/padding one which can only be solved with non-standard extensions like gcc __attribute__((__packed__))
or #pragma pack(1)
. But even if you use those, you end up with a chunk that the compiler may still access misaligned - you only told it to drop padding "I know what I'm doing". But if you go ahead and word access that memory, it may be a misaligned access.
Then there's the problem with variable-sized protocols. Resizing that memory chunk over and over adds depending on the amount of data received actually doesn't achieve much except bloat and program execution overhead. How much memory are you saving by doing so? Some 10 to 100 bytes? That's nothing even in low end MCUs. Since you only need to keep a few frames in RAM at the same time.
It turns out that you are going to have to allocate enough memory to store the largest frame ever appearing, since your program must handle that worst case. And then you could as well allocate that much memory to begin with, statically. Much faster, safer, deterministic.
And then there's yet another problem which you don't seem to address, namely network endianess. Modbus uses big endian and CRC are calculated in big endian. So the uint16_t
member at the end of the struct just sits there to create problems. Even if you would decide to use some non-standard GNU VLA extension in order to resize each frame.
I would advise you to forget all about these structs.
The fast, portable and safe solution is to simply use a uint8_t frame [MAX];
where MAX
is the maximum size in bytes that a frame could ever have. Using a struct just to give a variable name to one specific byte in the frame doesn't actually add anything in itself. What you really want is to have readable code easily explaining what each byte does, rather than an anonymous buffer of raw data.
This could as well be done with named indices (for example enum
) of this uint8_t
array when accessing it. There's no difference in readability, purpose or machine code generated between struct version frame.slave_addr = x;
and array version frame[slave_addr] = x;
. (Except the former might cause misaligned access in the machine code.)
You'll need to access the CRC byte by byte anyway, since you first need to calculate it with your CPU endianess, then convert it to network endianess. For example:
frame[fcs_high] = checksum >> 8;
frame[fcs_low] = checksum & 0xFF;
This code doesn't depend on CPU endianess unlike the struct, which will only work as expected on big endian CPUs.