HOME BLOG ARCHIVE TAGS

Flexible C Arrays

August 22, 2015

Maybe this belongs to beginners’ notes. In any case, here it goes.

There’s an old C language idiom, very useful when we have to deal with [de]serializable blobs:

1
2
3
4
5
6
struct blob_hdr_
{
    uint32_t ver;
    uint32_t len;
    uint8_t  data[1]; // <-- weird array len == 1;
};

Above declaration of data member looks strange, but there’s nothing wrong at all. blob_hdr_ struct happens to be a decoding facility, handy to simplify the manipulation of variable length structures:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
uint8_t b1[128];
uint8_t b2[64];

(...)

blob_hdr_ *p1 = (blob_hdr_ *) b1;
blob_hdr_ *p2 = (blob_hdr_ *) b2;

//
// clean/easy access to data element, after [ver + len] hdr;
//
do_something(p1->len, p1->data);
do_something(p2->len, p2->data);

This array-of-length-one declaration can be found in legacy/portable code, and is good enough for parsing. When serializing, things start to get uglier, cause this extra byte must be accounted for:

1
2
3
4
5
//
// note the -1 adjustment below;
//
blob_hdr_ *p1 = (blob_hdr_ *) malloc(sizeof(struct blob_hdr_) -1 + len1);
blob_hdr_ *p2 = (blob_hdr_ *) malloc(sizeof(struct blob_hdr_) -1 + len2);

If we adjust, the code looks messy and error prone (what we were trying to avoid, in the first place); if we don’t, space is wasted, and data handling is shifted one position.

A nice GCC extension helps here:

1
2
3
4
5
6
struct blob_hdr_
{
    uint32_t ver;
    uint32_t len;
    uint8_t  data[0]; // <-- last element is zero-length;
};

With zero-length arrays, both serialization and deserialization are covered, and blob_hdr_ is 8 bytes long.

If C99 is available, no extension needs to be employed. A flexible array member is the standardized way to go:

1
2
3
4
5
6
struct blob_hdr_
{
    uint32_t ver;
    uint32_t len;
    uint8_t  data[]; // <-- last element is a flexible array member;
};

Unfortunately, C++ doesn’t support flexible array members, and the first idiom may have to be used instead.