Flexible Array Members in C99

Actually, the title should omit C++. The flexible array is a feature introduced in C99.

Introduction

A Flexible Array Member (FAM) is a feature introduced in the C99 standard that allows the last member of a structure to be an array of unspecified size. This design is typically used to implement variable-length data structures, such as dynamic buffers and message packets.

Usage

struct Packet {
    int type;
    int length;
    char data[];  // Flexible array member
};

Since <span>data[]</span> has no size, you cannot directly define a variable of this structure, but you can dynamically allocate memory. For example, if I want the array to have 100 elements, I can do a malloc like this:

#include <stdlib.h>
#include <memory.h>

int data_size = 100;
struct Packet *pkt = malloc(sizeof(struct Packet) + data_size * sizeof(char));
if (pkt) {
    pkt->type = 1;
    pkt->length = data_size;
    strcpy(pkt->data, "Hello, world!");
}
// Free after use
free(pkt);

If you use C++, the above code will not compile.

fm.cc: In function 'int main()':
fm.cc:13:32: error: invalid conversion from 'void*' to 'Packet*' [-fpermissive]
     struct Packet *pkt = malloc(sizeof(struct Packet) + data_size * sizeof(char));
                          ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In C++, <span>malloc</span> returns a <span>void*</span> type. C++ does not allow implicit conversion from <span>void*</span> to other pointer types for type safety. However, C allows this.

If you are using C++, this line of code needs a cast:

struct Packet *pkt = static_cast<Packet*>(malloc(sizeof(struct Packet) + data_size * sizeof(char)));

ISO C++ does not support flexible array members, but gcc can compile it here. Quoting the original text from the gcc website: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html#Zero-Length

The GCC extension accepts a structure containing an ISO C99 flexible array member, or a union containing such a structure (possibly recursively) to be a member of a structure.

This means that gcc only introduces support for flexible arrays as an extension to C, not as part of the C++ standard.

Advantages

Everyone is using it, so it is unlikely that flexible arrays are difficult to use, causing people to intentionally torture themselves. Flexible arrays have performance advantages that alternatives cannot match.

Let’s imagine how the above code would look if we did not use a flexible array but instead used pointers:

int main()
{
    int data_size = 100;
    struct Packet *pkt = static_cast<Packet*>(malloc(sizeof(struct Packet)));
    if (pkt)
    {
        pkt->type = 1;
        pkt->length = data_size;
        char *data = static_cast<char*>(malloc(data_size * sizeof(char)));
        strcpy(pkt->data, "Hello, world!");
    }
    // Free after use
    free(pkt->data);
    free(pkt);
}

This presents two issues:

  1. It cannot guarantee that the memory for <span>data</span> is contiguous with the first two members. This will reduce CPU cache hit rates. Additionally, it complicates serialization (for example, using <span>memcpy</span> to copy the entire block).
  2. Compared to the flexible array approach, it allocates/releases memory one extra time (for <span>data</span>), which not only degrades performance but also requires the external code to allocate/release memory twice when the structure is encapsulated externally (only for C, since you cannot write member functions for structures…).

Issues

The biggest issue is that the general <span>sizeof</span> does not apply to structures with flexible array members.

For example, out-of-bounds access:

pkt->data[200] = 'a';  // This will go out of bounds if only 100 bytes were allocated

Incorrect memory size calculation:

malloc(sizeof(struct Packet) + data_size * sizeof(char));  // Correct
// But it can easily be mistakenly written as:
malloc(sizeof(struct Packet));  // Error: data not allocated space

When using memcpy, care must also be taken. In short, the design of flexible arrays reflects the design philosophy of C, which is to give programmers ample power, but requires them to pay attention to memory allocation issues. However, this issue cannot be overlooked.

Notes

The C99 standard itself provides guidelines for the use of flexible arrays in section 6.7.2.1 §18:

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.

First, it should be noted that <span>char data[]</span> is not a pointer. <span>data[]</span> is an array type, but its size is unspecified (unspecified). It is not a pointer (char*), nor is it an array of length 0. It is an “incomplete type” until you dynamically allocate memory for it.

Similar to <span>extern char data[]</span>, using <span>sizeof</span> on it is illegal. Most of the following notes are based on this principle.

FAM Must Be the Last Member

❌ Illegal Example:

struct Bad {
    char data[];  // ❌ There are more members after this
    int crc;      // Error: FAM is not at the end
};

Structures Containing FAM Must Have At Least Two Named Members

❌ Illegal Example:

struct OnlyFAM {
    char data[];  // ❌ Error: Only one member (even if it is FAM)
};

Cannot Calculate the Size of the Flexible Array Itself (sizeof Illegal)

❌ Illegal Example:

struct Packet pkt;
sizeof(pkt.data);     // ❌ Error: Cannot use sizeof on FAM

Cannot Define an Array of Structures Containing FAM

❌ Illegal Example:

struct Packet {
    int type;
    char data[];
};

struct Packet packets[10];  // ❌ Compilation error

Structures Containing FAM Cannot Be Members of Another Structure

❌ Illegal Example:

struct Inner {
    int len;
    char data[];
};

struct Outer {
    int tag;
    struct Inner inner;  // ❌ Error: FAM structure cannot be nested
};

Cannot Use sizeof to Allocate Memory for Structures Containing FAM (Must Calculate Manually)

❌ Illegal Example:

struct Packet *pkt = malloc(sizeof(struct Packet));             // ❌ data[] has no space

Cannot Directly Initialize or Assign Structures Containing FAM

❌ Illegal Example:

struct Packet p = {1, "hello"};  // ❌ Error: Cannot initialize FAM
struct Packet q = p;             // ❌ Error: Cannot assign

Leave a Comment