A flexible array member is an array without a specified size. The member has to be the last member of the struct. The actual array size is set when you allocate memory for the struct. Consequently, it only makes sense together with dynamic allocation.
Example:
#define ARRSIZE 10
struct FAM
{
size_t sz;
int arr[]; // Must be last struct member
};
struct FAM* fam = malloc(sizeof(struct FAM) + ARRSIZE * sizeof(int));
Now fam->arr
is an array with ARRSIZE
elements that you can access ussing fam->arr[index]
.
Further code like:
struct FAM* famA = malloc(sizeof(struct FAM) + 10 * sizeof(int));
struct FAM* famB = malloc(sizeof(struct FAM) + 1000 * sizeof(int));
will give you two pointers of the same type even though the size of the arrays differ.
So why would I use it?
Look at this code
struct FAM
{
size_t sz;
int arr[];
};
struct P
{
size_t sz;
int* p;
};
int getFAM(struct FAM* f, unsigned long x)
{
return f->arr[x];
}
int getP(struct P* p, unsigned long x)
{
return p->p[x];
}
It's two ways of doing "the same". One using a flexible array member, the other using a pointer.
Compiled with gcc -O2 on https://godbolt.org/ I get

which indicates that the flexible array can save you one indirection and thereby generate faster code.
It can be described like: In case of a flexible array member the array has fixed offset from a struct pointer while in case of a pointer-to-int member the array is located where the pointer value says. Consequently, in the first case the compiler can use that "known fixed offset" directly but in the second case the compiler must read the pointer value first and then read the array data.
Note: This example is for one specific system (aka compiler/cpu type). On other systems the result may differ.