1

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?

morimn
  • 513
  • 3
  • 10
  • 2
    I don't understand your question. What do you want to be pedantic about? What's the problem you're solving? – Marcus Müller Nov 10 '21 at 10:54
  • @MarcusMüller There's no problem in using the code. What I am curious about is if there is a more standard-conforming way to write that. (because an array with zero size is as per standard, illegal) – morimn Nov 10 '21 at 10:56
  • 1
    Could you edit your question to state that? – Marcus Müller Nov 10 '21 at 10:58
  • 2
    Ditch the union, keep track of what kind of data you are storing, then use a flexible array member of `uint8_t`. – Lundin Nov 10 '21 at 10:59
  • @AdrianMole from the name of the type, we can assume that it's data coming from external sources, i.e. a network packet buffer being cast to a `packet_t`. – Marcus Müller Nov 10 '21 at 11:03
  • This definitely can't be used to map a network packet because of endianess. You'd need a serialize/deserialize routine. – Lundin Nov 10 '21 at 11:22
  • 1
    @Lundin Certainly. This is a small snitch of the library which does have a serializer :) – morimn Nov 10 '21 at 11:33
  • @morimn Have the serializer pick the suitable type to store the data in. You should be able to solve the actual problem with proper program design instead of obscure unions. – Lundin Nov 10 '21 at 11:35

4 Answers4

3

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
0

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.

Marcus Müller
  • 34,677
  • 4
  • 53
  • 94
0

So this is a packet with flexible-lengthed data, with an access to each of the word lengths.

You cannot define variable sized arrays in C (in general, in special cases you can, but are not common). The language doesn't have any way to change dynamically the size of an array, as the length of an array is associated to its declaration (which must be known at compilation time) there's no such a thing as type parameters that allow you to specify the number of elements the array will have in the structure when you are declaring a variable of such type.

The workaround comes with pointers. You can declare an array element type pointer, and dynamically allocate memory for it, as in:

typedef struct {
    uint16_t id;
    uint16_t len;
    union {
        uint8_t *data;
        uint16_t *data16;
        uint32_t *data32;
    } payload;  /* you need to name the union field!!! */
} packet_t;

and initialize the pointers with a call to malloc(), giving the proper amount of memory. Later, if you change your mind, you can free() the pointer and allocate anocher (again, with malloc()) and a different size.

    packet_t pkt;
    pkt.id = 23;
    pkt.len = 113;
    pkt.payload.data = malloc[pkt.len];
    ....
    /* access */
    printf("third 16bit data in the payload is: 0x%04x\n",
            pkt.payload.data16[2]);
Luis Colorado
  • 10,974
  • 1
  • 16
  • 31
-1

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

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • Flexible array members were introduced because dirty code such as this ("the struct hack") was always undefined behavior even in C90. So this will always be the wrong solution no matter C version. – Lundin Nov 10 '21 at 11:37
  • When would I be concerned with the "add/subtract by 1"? Doesn't sizeof() give 0 for zero-size arrays, not the actual length of the data anyway? – morimn Nov 10 '21 at 11:38
  • 1
    @morimn `sizeof` gives the size of the header plus 1 element only, so subtract that element size to get the header size – phuclv Nov 10 '21 at 13:27