Flexible Array Member (FAM) adalah fitur C99 yang memungkinkan kita mendeklarasikan array tanpa ukuran di akhir struct, lalu mengalokasikan memori tambahan untuk elemen array tersebut saat runtime. Pola ini menggantikan trik lama “struct hack” dengan data[1] yang tidak portabel dan sering melanggar aliasing rule. FAM sangat berguna untuk buffer variabel, packet jaringan, string buffer, dan struktur data lain yang ukurannya hanya diketahui saat runtime. Pada chapter ini kita akan membangun Buffer dengan int data[] dan Packet dengan payload byte variabel.

Dasar FAM: Buffer dengan int data[] #

FAM dideklarasikan dengan menulis array kosong T data[] di akhir struct. Saat alokasi, kita memakai malloc(sizeof(struct) + n * sizeof(T)) untuk mendapatkan ruang bagi n elemen. sizeof(struct) tidak menghitung ukuran array—array dianggap memiliki ukuran nol oleh sizeof.

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Struct dengan flexible array member di akhir */
struct Buffer {
    size_t n;
    int data[];     /* FAM: tidak ada ukuran */
};

/* Buat buffer dengan n elemen */
struct Buffer *buf_create(size_t n) {
    /* Alokasi: sizeof struct + ruang n elemen */
    struct Buffer *b = malloc(sizeof(struct Buffer) + n * sizeof(int));
    if (!b) return NULL;
    b->n = n;
    for (size_t i = 0; i < n; i++) b->data[i] = (int)(i * i);
    return b;
}

int main(void) {
    printf("sizeof(struct Buffer) = %zu (tanpa data)\n", sizeof(struct Buffer));

    struct Buffer *b1 = buf_create(3);
    if (!b1) return 1;
    printf("b1->n = %zu, data = ", b1->n);
    for (size_t i = 0; i < b1->n; i++) printf("%d ", b1->data[i]);
    printf("\n");
    free(b1);

    struct Buffer *b2 = buf_create(5);
    if (!b2) return 1;
    printf("b2->n = %zu, data = ", b2->n);
    for (size_t i = 0; i < b2->n; i++) printf("%d ", b2->data[i]);
    printf("\n");

    /* Salin data dengan memcpy -- perlu total size */
    struct Buffer *b3 = malloc(sizeof(struct Buffer) + b2->n * sizeof(int));
    if (!b3) { free(b2); return 1; }
    memcpy(b3, b2, sizeof(struct Buffer) + b2->n * sizeof(int));
    printf("b3 (copy b2): ");
    for (size_t i = 0; i < b3->n; i++) printf("%d ", b3->data[i]);
    printf("\n");

    free(b2);
    free(b3);
    return 0;
}

Output yang Dihasilkan:

text
sizeof(struct Buffer) = 8 (tanpa data)
b1->n = 3, data = 0 1 4 
b2->n = 5, data = 0 1 4 9 16 
b3 (copy b2): 0 1 4 9 16 

Mari kita telaah baris-baris kode di atas:

  1. Baris 7-10 (struct Buffer { size_t n; int data[]; };): deklarasi FAM. data[] tidak punya ukuran—array ini hanya boleh muncul di akhir struct dan harus menjadi member terakhir. Kompiler menambahkan padding bila perlu, tetapi sizeof(struct Buffer) mengabaikan data.
  2. Baris 14 (malloc(sizeof(struct Buffer) + n * sizeof(int))): alokasi untuk n elemen int di luar struct. Tanpa penambahan ini, akses b->data[0] akan menulis ke memori di luar alokasi—undefined behavior.
  3. Baris 16 (b->data[i] = (int)(i * i);): akses FAM seperti array biasa. Karena memori sudah dialokasikan, akses aman sampai indeks n-1.
  4. Baris 22 (sizeof(struct Buffer)): pada output menunjukkan 8, yaitu ukuran size_t saja (size_t = 8 byte di 64-bit). Array data tidak dihitung.
  5. Baris 35-36 (memcpy(b3, b2, sizeof(struct Buffer) + b2->n * sizeof(int))): menyalin seluruh struct termasuk array. Tanpa ukuran tambahan, memcpy hanya akan menyalin 8 byte header dan meninggalkan data.

FAM untuk Packet Jaringan dengan Payload Variabel #

Pola umum FAM adalah packet header tetap diikuti payload yang ukurannya bervariasi. Header berisi metadata (id, flags, panjang), payload berisi data mentah. FAM memungkinkan satu alokasi menyimpan keduanya—lebih efisien daripada dua alokasi terpisah (header + payload) yang memerlukan pointer tambahan dan rapuh terhadap double free.

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Header packet jaringan dengan payload variabel */
typedef struct {
    int id;
    int flags;
    size_t payload_len;
    unsigned char payload[];   /* FAM */
} Packet;

Packet *packet_create(int id, int flags, const unsigned char *data, size_t len) {
    Packet *p = malloc(sizeof(Packet) + len);
    if (!p) return NULL;
    p->id = id;
    p->flags = flags;
    p->payload_len = len;
    if (len > 0) memcpy(p->payload, data, len);
    return p;
}

void packet_print(const Packet *p) {
    printf("Packet id=%d flags=0x%X len=%zu payload=", p->id, p->flags, p->payload_len);
    for (size_t i = 0; i < p->payload_len; i++) printf("%02X ", p->payload[i]);
    printf("\n");
}

int main(void) {
    unsigned char data1[] = {0xDE, 0xAD, 0xBE, 0xEF};
    unsigned char data2[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};

    Packet *p1 = packet_create(1, 0x01, data1, sizeof(data1));
    Packet *p2 = packet_create(2, 0x02, data2, sizeof(data2));

    packet_print(p1);
    packet_print(p2);

    free(p1);
    free(p2);
    return 0;
}

Output yang Dihasilkan:

text
Packet id=1 flags=0x1 len=4 payload=DE AD BE EF 
Packet id=2 flags=0x2 len=8 payload=01 02 03 04 05 06 07 08 

Satu alokasi malloc(sizeof(Packet) + len) menyimpan header sekaligus payload. Bila header dan payload dialokasikan terpisah, kita perlu Packet *p plus unsigned char *payload terpisah—dua alokasi, dua free, dan potensi memory leak bila salah satu free terlupakan. FAM mengkonsolidasikan keduanya.

Aturan Penting FAM #

  • Hanya satu FAM per struct, dan harus menjadi member terakhir.
  • sizeof(struct) tidak menghitung FAM.
  • FAM tidak boleh muncul di struct yang menjadi elemen array (misalnya struct Buf arr[5] tidak valid karena ukuran tidak diketahui).
  • Akses ke data[i] di luar batas yang dialokasikan adalah undefined behavior—struct tidak menyimpan ukuran array, pemrogram harus melacaknya (di contoh kita, n atau payload_len).