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.
#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:
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:
- 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, tetapisizeof(struct Buffer)mengabaikandata. - Baris 14 (
malloc(sizeof(struct Buffer) + n * sizeof(int))): alokasi untuknelemenintdi luar struct. Tanpa penambahan ini, aksesb->data[0]akan menulis ke memori di luar alokasi—undefined behavior. - Baris 16 (
b->data[i] = (int)(i * i);): akses FAM seperti array biasa. Karena memori sudah dialokasikan, akses aman sampai indeksn-1. - Baris 22 (
sizeof(struct Buffer)): pada output menunjukkan8, yaitu ukuransize_tsaja (size_t= 8 byte di 64-bit). Arraydatatidak dihitung. - Baris 35-36 (
memcpy(b3, b2, sizeof(struct Buffer) + b2->n * sizeof(int))): menyalin seluruh struct termasuk array. Tanpa ukuran tambahan,memcpyhanya 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.
#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:
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,nataupayload_len).