In a library I'm working with I found a structure like this:
typedef struct {
uint16_t id;
uint16_t len;
union {
uint8_t data[0];
uint16_t data16[0];
uint32_t data32[0];
}
} packet_t;
So this is a packet with flexible-lengthed data, with an access to each of the word lengths.
But the standard says an object cannot have zero size, so compiling this raises a warning ("ISO C forbids zero-size array").
I compile the code in gcc so this zero-sized array hack will just do fine thanks to its extension. But is there any way I can be really "pedantic" about this? Flexible Array Member didn't help because of the union.
It will be simple if I just throw away the multibyte data members, but I want to keep them as much as possible because some functions in the library rely on these.
At first I thought I could use one-sized arrays instead, but I'm not sure if there is no side affects at all. Any recommendations?
CodePudding user response:
It's not impossible to produce something that compiles with the -pedantic flag. But I don't think you can get the exact same semantics syntax-wise without C11.
The transformed packet_t
may look like this
typedef union packet_t {
struct { uint16_t id; uint16_t len; };
struct { uint16_t id8; uint16_t len8; uint8_t data[]; };
struct { uint16_t id16; uint16_t len16; uint16_t data16[]; };
struct { uint16_t id32; uint16_t len32; uint32_t data32[]; };
} packet_t;
The anonymous struct (C11) is required to nestle the fields as members of packet_t
. It's what makes p->id
valid still. The other id*
and len*
fields are dummies, because a flexible array member may not be the only member of a structure. They are there to make the access well-formed according to the common initial sequence guarantee of unions.
Looking at it, I can't help but feel you may be better off ditching the multiple data
fields altogether. A uint8_t
flexible array member can contain whatever the other two may contain.
CodePudding user response:
Before C99, there's no standard-compliant way. In C99,
typedef struct {
uint16_t id;
uint16_t len;
uint8_t data[];
} packet_t;
is the legal way to do a flexible length struct.
Your union
was nearly useless synthetic sugar, anyways: you needed to know the type externally anyways, and C is not type-restrictive, so whether you write packet.data16[2]
or uint16_t* data = packet.data; data[2]
makes jolly little difference.
There's no standards-compliant way to keep an array of union
type with different member lengths as the last element.
CodePudding user response:
If you use a pre-C99 compiler then a simple common trick is to set the size to 1. Of course you also need to add/subtract the size by 1 when handling it
typedef struct {
uint16_t id;
uint16_t len;
union {
uint8_t data[1];
uint16_t data16[1];
uint32_t data32[1];
}
} packet_t;
See also