A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
A flexible array member is an array declared with empty brackets as the last member of a struct: int data[]. It has no size at compile time. You allocate the struct plus extra space for the array elements in a single malloc call. The struct header and the array data are contiguous in memory. One allocation. One free. No pointer indirection. This is how C implements variable-length records: network packets, database rows, string buffers, and filesystem entries.
#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct {size_t length;char data[];} String;String *string_create(const char *text) {size_t len = strlen(text);String *s = malloc(sizeof(String) + len + 1);if (!s) return NULL;s->length = len;memcpy(s->data, text, len + 1);return s;}int main(void) {String *greeting = string_create("Hello, flexible arrays!");if (!greeting) return 1;printf("Length: %zu\n", greeting->length);printf("Data: \"%s\"\n", greeting->data);printf("sizeof(String): %zu (header only)\n", sizeof(String));free(greeting);return 0;}
sizeof(String) is 8: just the length field. The flexible array member contributes zero to the struct size. The actual allocation is sizeof(String) + len + 1, which includes space for the string data and its null terminator. The header and the data are in one contiguous block. One malloc. One free. No separate allocation for the character data. No pointer to chase.
The flexible array member must be the last member of the struct. The struct must have at least one other member. You cannot declare an array of structs with flexible array members. You cannot use it as a value type; it must be allocated dynamically or as part of a larger allocation. These restrictions ensure the compiler can compute the header size at compile time while the array size is determined at runtime.
#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct {unsigned short type;unsigned short length;unsigned char payload[];} Packet;Packet *packet_create(unsigned short type, const void *data, unsigned short len) {Packet *p = malloc(sizeof(Packet) + len);if (!p) return NULL;p->type = type;p->length = len;memcpy(p->payload, data, len);return p;}int main(void) {unsigned char msg[] = {0xDE, 0xAD, 0xBE, 0xEF};Packet *pkt = packet_create(0x01, msg, sizeof(msg));printf("Type: 0x%04X\n", pkt->type);printf("Length: %u\n", pkt->length);printf("Payload: ");for (int i = 0; i < pkt->length; i++) {printf("%02X ", pkt->payload[i]);}printf("\n");free(pkt);return 0;}
The packet header is 4 bytes (two unsigned shorts). The payload follows immediately in the same allocation. This is exactly how network protocols, file formats, and kernel data structures lay out variable-length records. The header tells you the type and length. The payload follows. One allocation. One free. Zero fragmentation.
Flexible array members are the C standard's answer to variable-length records. They eliminate the two-allocation pattern (allocate struct, allocate data, link with pointer) and replace it with a single contiguous block. Use them for packets, messages, strings, and any record where the trailing data has a runtime-determined length. Structs and unions module complete. Next: the preprocessor, where we meet macros, conditional compilation, and the text-substitution engine that runs before the compiler sees your code.
Finish the lesson once you have worked through the material. This awards ★ 40 XP.