C Mastery / Flexible Array Members and Variable-Length Arrays
Part 1 — The Core Language

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

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

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

included (it is not).

Undefined Behavior

Portability

size-1 array member, which is UB if accessed out of bounds).

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

string-bearing structs.

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.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.struct.fam06