Sejak C99, C mendukung flexible array member (FAM)—array tanpa ukuran di akhir struct yang dapat tumbuh sesuai kebutuhan. FAM menggantikan trik lama “struct hack” (int data[1]) yang tidak portabel dan bergantung pada undefined behavior. Dengan FAM, kita dapat mengalokasikan satu blok malloc yang berisi struct header dan array variabel dalam satu alokasi—lebih efisien daripada dua alokasi terpisah (header dan array). FAM sangat berguna untuk buffer panjang variabel, string dinamis, dan struktur data seperti dynamic array. Pada chapter ini kita akan mempraktikkan FAM dengan dua contoh konkret.
Apa itu Flexible Array Member #
Standar C (§6.7.2.1¶18) mendefinisikan FAM sebagai field terakhir struct berupa array tanpa ukuran:
struct Buf {
size_t n; /* field biasa */
int data[]; /* flexible array member -- HARUS field terakhir */
};Karakteristik FAM:
- Hanya boleh ada satu FAM per struct, dan harus berada di posisi terakhir.
sizeof(struct Buf)tidak termasuk FAM—sizeofhanya menghitung field biasa.- FAM tidak dapat diakses langsung lewat instance biasa—hanya valid bila struct dialokasikan dengan
malloc(sizeof(struct Buf) + N * sizeof(elemen)). - Alignment: FAM mewarisi alignment struct. Pada
struct Bufdenganint data[], FAM dijamin mulai di alamat yang alignment-nya cocok untukint.
Mekanisme kerja: saat kita menulis malloc(sizeof(struct Buf) + n * sizeof(int)), allocator mengembalikan blok yang cukup besar untuk header struct Buf (8 byte pada sistem 64-bit: size_t 8 byte) dan n * sizeof(int) byte untuk data. Field data otomatis menempati byte-byte setelah n—karena FAM selalu di akhir struct, tidak ada padding tambahan setelah FAM. Satu alokasi, satu free, satu cache miss.
Contoh: Buffer Integer Variabel #
Kita buat struct Buf dengan size_t n dan int data[], lalu fungsi buat_buf(n) yang mengalokasikan struct + array dalam satu blok.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Flexible array member: int data[] tanpa ukuran */
struct Buf {
size_t n; /* jumlah elemen */
int data[]; /* flexible array member -- selalu field terakhir */
};
struct Buf *buat_buf(size_t n) {
/* Alokasi: sizeof(struct Buf) + n * sizeof(int) */
size_t total = sizeof(struct Buf) + n * sizeof(int);
struct Buf *b = malloc(total);
if (b == NULL) return NULL;
b->n = n;
for (size_t i = 0; i < n; i++) {
b->data[i] = (int)(i * i); /* kuadrat */
}
return b;
}
int main(void) {
printf("sizeof(struct Buf) = %zu (hanya header, tanpa data)\n",
sizeof(struct Buf));
struct Buf *b = buat_buf(5);
if (b == NULL) {
fprintf(stderr, "malloc gagal\n");
return 1;
}
printf("\nIsi buffer (n=%zu):\n", b->n);
for (size_t i = 0; i < b->n; i++) {
printf(" data[%zu] = %d\n", i, b->data[i]);
}
free(b);
return 0;
}Output yang Dihasilkan:
sizeof(struct Buf) = 8 (hanya header, tanpa data)
Isi buffer (n=5):
data[0] = 0
data[1] = 1
data[2] = 4
data[3] = 9
data[4] = 16Perhatikan bahwa sizeof(struct Buf) adalah 8 byte—hanya size_t n, tanpa data[]. FAM tidak dihitung dalam sizeof. Saat buat_buf(5) dipanggil, kita mengalokasikan sizeof(struct Buf) + 5 * sizeof(int) = 8 + 20 = 28 byte—satu alokasi berisi header dan array. Setelah malloc, kita dapat mengakses b->data[0] hingga b->data[4] sebagaimana array biasa.
Mari kita telaah baris-baris kode di atas:
- Baris 6 (
struct Buf { size_t n; int data[]; };): deklarasi struct dengan FAMint data[]. Fielddatatidak memiliki ukuran—ukurannya ditentukan olehmalloc. Standar C99 mengharuskan FAM adalah field terakhir. - Baris 11 (
size_t total = sizeof(struct Buf) + n * sizeof(int);): menghitung ukuran total alokasi.sizeof(struct Buf)adalah ukuran header (8 byte),n * sizeof(int)adalah ruang untuknelemenint. - Baris 12 (
struct Buf *b = malloc(total);): satu panggilanmallocuntuk seluruh blok—header + array. Lebih efisien daripada dua alokasi terpisah. - Baris 14 (
b->n = n;): mengisi fieldndengan jumlah elemen. Field biasa diakses seperti struct biasa. - Baris 16 (
b->data[i] = (int)(i * i);): menulis ke FAM. Karenadataadalahint[], aksesb->data[i]setara dengan*(b->data + i)—pointer ke blok setelah header. - Baris 26 (
sizeof(struct Buf)): cetak ukuran header saja. FAM tidak dihitung. Hasil 8 byte menunjukkan tidak ada padding untuk FAM. - Baris 33 (
free(b);): satufreeuntuk seluruh blok. Tidak perlufreeterpisah untuk array—array adalah bagian dari blok yang sama.
Contoh: String Variabel dengan FAM #
FAM cocok untuk string panjang variabel. Kita buat struct String dengan panjang dan char isi[]—satu alokasi berisi metadata dan isi string.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Flexible array member untuk string variabel */
struct String {
size_t panjang;
char isi[]; /* flexible array of char */
};
struct String *buat_string(const char *s) {
size_t panjang = strlen(s);
struct String *str = malloc(sizeof(struct String) + panjang + 1);
if (str == NULL) return NULL;
str->panjang = panjang;
memcpy(str->isi, s, panjang + 1); /* salin termasuk '\0' */
return str;
}
int main(void) {
struct String *s1 = buat_string("Belajar C");
struct String *s2 = buat_string("Flexible array member");
if (s1 && s2) {
printf("s1: \"%s\" (panjang %zu)\n", s1->isi, s1->panjang);
printf("s2: \"%s\" (panjang %zu)\n", s2->isi, s2->panjang);
printf("sizeof(struct String) = %zu\n", sizeof(struct String));
}
free(s1);
free(s2);
return 0;
}Output yang Dihasilkan:
s1: "Belajar C" (panjang 9)
s2: "Flexible array member" (panjang 21)
sizeof(struct String) = 8Struct String menghemat memori dibanding alternatif struct { size_t panjang; char *isi; } yang memerlukan dua alokasi (satu untuk struct, satu untuk string) dan dua free. Dengan FAM, satu alokasi berisi semuanya—lebih sedikit overhead, lebih sedikit cache miss, lebih mudah cleanup.
Perhatikan panjang + 1 pada perhitungan malloc—kita perlu byte tambahan untuk \0 penutup string. memcpy(str->isi, s, panjang + 1) menyalin termasuk \0 sehingga str->isi dapat dipakai sebagai string C biasa.