Flexible Array Members and Variable-Length Arrays
This chapter covers flexible array members (FAMs), a C99 feature for variable-sized structs, and reviews variable-length arrays (VLAs) in contrast. Both address the problem of "an object whose size is not known at compile time."
Why This Matters
Flexible array members are the idiomatic, standard-conforming way to allocate a struct with a trailing variable-length payload in one contiguous block. VLAs, by contrast, are convenient but risky and not universally supported.
Prerequisites
c.core.22— arrays, VLAs.c.core.26— structs and alignment.
Core Concept
Flexible array member (FAM)
A flexible array member is the last member of a struct, declared with empty brackets []:
struct Buffer {
size_t len;
char data[]; /* flexible array member */
};
data contributes zero to sizeof(struct Buffer). You allocate the struct with extra space for the array:
size_t payload = 64;
struct Buffer *b = malloc(sizeof *b + payload);
b->len = payload;
/* b->data[0 .. payload-1] is usable */
The struct and its trailing array are contiguous, so one allocation holds everything and one free releases it.
Rules for FAMs
- The FAM must be the last member.
- The struct must have at least one other member.
- The struct must not be an element of an array.
sizeofthe struct excludes the FAM.
Syntax
struct Name {
type regular_member;
type flexible_array[];
};
Examples
Allocating a struct with a FAM
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Message {
size_t len;
char text[];
};
int main(void)
{
const char *src = "hello";
size_t n = strlen(src) + 1; /* include null terminator */
struct Message *m = malloc(sizeof *m + n);
if (m == NULL) return 1;
m->len = n;
memcpy(m->text, src, n);
printf("%zu %s\n", m->len, m->text);
free(m);
return 0;
}
Expected output: 6 hello.
How It Works
A FAM is a compile-time marker, not a real array object with a size. The compiler knows its element type and offset (which is the struct size, rounded up for alignment), and indexing m->text[i] compiles to access at m + offset + i*sizeof(char). The actual storage comes from the extra bytes you allocate.
Variations
FAM vs. pointer member
struct PtrVersion {
size_t len;
char *data; /* separate allocation; pointer is part of struct */
};
The pointer version stores the payload elsewhere (possibly non-contiguous), uses more memory (a pointer plus a separate allocation), and requires separate free. The FAM is contiguous and single-allocation.
FAM vs. VLA
A VLA is an automatic array whose size is a run-time expression. It lives on the stack. A FAM lives wherever you allocate the struct (usually heap). FAMs are the standard way to get variable-sized *heap* objects; VLAs are a stack-oriented feature.
Common Mistakes
- Declaring a FAM not as the last member.
- Computing
sizeofincluding the FAM and expecting the payload to be
included (it is not).
- Forgetting to allocate extra bytes for the FAM.
- Trying to make an array of structs with a FAM (not allowed).
Undefined Behavior
- Accessing a FAM element beyond the allocated extra space.
VERIFIED - Declaring a struct with a FAM as an array element (constraint violation).
- Using
sizeofon a FAM as if it included payload.
Portability
- FAMs are C99 and later. C89 did not have them (the common pre-C99 hack was a
size-1 array member, which is UB if accessed out of bounds).
- The alignment of the FAM is the same as its element type; allocate enough
for alignment (the sizeof *m + n pattern handles this correctly because sizeof *m already includes the struct's alignment padding).
Under the Hood
The FAM has no size in the struct; it is essentially an offset. The allocation sizeof *m + n reserves n bytes immediately after the struct's fixed part, and the FAM accesses those bytes.
Practical Usage
- Use FAMs for message buffers, packet headers with variable payloads, and
string-bearing structs.
- Prefer FAMs over the pre-C99 size-1 array hack.
- Use the
sizeof *p + nallocation idiom.
Exercises
1. Define a struct with a FAM and allocate it for various payload sizes; verify sizeof excludes the FAM. 2. Compare a FAM version with a pointer-member version and explain the layout and lifetime differences. 3. Write a function that creates a FAM struct from a string and frees it.
Deep Challenge
Design a small binary protocol header with a fixed header and a variable-length body using a FAM, and write encode/decode functions that are robust against length and alignment issues. Explain why the FAM layout is preferable to a separate heap allocation for wire-format parsing.
Related Concepts
c.mem.malloc— allocation.c.arr.vla— VLAs.c.obj.alignment— alignment in allocation.
References
- ISO/IEC 9899:2018 §6.7.2.1 (struct/union, flexible array members).
Verification
- FAM contributes zero to sizeof.
VERIFIED - FAM must be last member.
VERIFIED sizeof *m + nallocation idiom.VERIFIED- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Flexible array member syntax
- [ ] FAM allocation idiom
- [ ] FAM vs. pointer member
- [ ] FAM vs. VLA
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.struct.fam | 0 | 6 |