struct member biasanya menempati memori sesuai tipe: int 4 byte, char 1 byte. Namun ada situasi ketika kita hanya perlu menyimpan sedikit bit—sebuah flag 1 bit, sebuah mode 2 bit (0..3), sebuah versi 4 bit (0..15). Mengalokasikan int penuh untuk flag 1 bit jelas boros. C menyediakan bit-fields: member struct yang lebarnya ditentukan dalam bit. Bit-fields bermanfaat untuk merepresentasikan register perangkat keras, header protokol jaringan, atau sekadar menghemat memori pada struktur data berukuran raksasa. Pada chapter ini kita akan mendefinisikan bit-fields, mengamati rentang nilainya, dan melihat perilaku saat nilai melebihi kapasitas.
Apa Itu Bit-field #
Bit-field adalah member struct yang dideklarasikan dengan sintaks tipe nama : lebar;. Tipe harus int, unsigned int, signed int, atau (sejak C23) tipe bilangan bulat lain seperti bool dan unsigned char. Lebar adalah jumlah bit, harus berupa konstanta non-negatif. Member seperti unsigned b : 3; hanya menyimpan 3 bit, sehingga rentang nilainya adalah 0..7 (untuk unsigned) atau -4..3 (untuk signed 3-bit).
Mekanisme kerja di balik layar: compiler mengemas beberapa bit-field ke dalam satu unit penyimpanan (biasanya int) sebanyak mungkin, dan mengocor ke unit berikutnya bila tidak muat. Tata letak bit-field (urutan MSB-first atau LSB-first, aturan packing) bersifat implementation-defined—bisa berbeda antara GCC, Clang, dan MSVC, serta antara arsitektur little-endian dan big-endian. Karena itu, bit-field tidak boleh dipakai untuk membaca/menulis data biner lintas-platform tanpa dokumentasi yang hati-hati.
Mendefinisikan struct dengan Bit-fields #
Kita mulai dengan contoh dasar: struct Flags dengan tiga bit-field berbeda lebar. Tujuannya adalah mengamati rentang nilai dan perilaku saat assignment melebihi kapasitas.
#include <stdio.h>
/* struct dengan bit-fields */
struct Flags {
unsigned a : 1; /* 1 bit: rentang 0..1 */
unsigned b : 3; /* 3 bit: rentang 0..7 */
unsigned c : 4; /* 4 bit: rentang 0..15 */
};
int main(void) {
struct Flags f = {0};
printf("sizeof(struct Flags) = %zu byte\n", sizeof(struct Flags));
/* Mengisi nilai valid */
f.a = 1; /* max untuk 1 bit */
f.b = 7; /* max untuk 3 bit */
f.c = 15; /* max untuk 4 bit */
printf("a=%u b=%u c=%u (semua nilai maksimum)\n", f.a, f.b, f.c);
/* Mencoba nilai di luar rentang: hanya low bits yang dipakai */
f.b = 9; /* 9 dalam biner = 1001, hanya 001 yang tersimpan */
printf("Setelah f.b = 9 -> a=%u b=%u c=%u\n", f.a, f.b, f.c);
f.b = 13; /* 13 = 1101, tersimpan 101 = 5 */
printf("Setelah f.b = 13 -> a=%u b=%u c=%u\n", f.a, f.b, f.c);
/* Cek apakah bit ke-x set */
f.b = 6; /* biner 110 */
printf("f.b = %u, bit terendah: %d, bit tengah: %d, bit tertinggi: %d\n",
f.b, (f.b & 1) != 0, (f.b & 2) != 0, (f.b & 4) != 0);
return 0;
}Output yang Dihasilkan:
sizeof(struct Flags) = 4 byte
a=1 b=7 c=15 (semua nilai maksimum)
Setelah f.b = 9 -> a=1 b=1 c=15
Setelah f.b = 13 -> a=1 b=5 c=15
f.b = 6, bit terendah: 0, bit tengah: 1, bit tertinggi: 1Mari kita telaah baris-baris kode di atas:
- Baris 5-9 (
struct Flags { unsigned a : 1; ... };): tiga bit-field dengan total 1 + 3 + 4 = 8 bit. Compiler mengemas ketiganya ke dalam satu unit penyimpanan, sehingga ukuranstruct Flagshanya 4 byte—lebih hemat daripada tigaunsigned intterpisah (12 byte). Lebih besar dari 1 byte karena alignment minimum unit penyimpanan. - Baris 13 (
struct Flags f = {0};): inisialisasi aggregate. Membera,b,csemua diisi 0. Inisialisasi awal diperlukan karena bit-field tidak boleh diambil alamatnya (&f.badalah compile error), sehingga kita tidak bisa memakai pointer untuk membersihkannya. - Baris 16-19 (
f.a = 1; f.b = 7; f.c = 15;): assignment nilai maksimum untuk masing-masing lebar.1(1 bit) cocok untuka(rentang 0..1);7(3 bit) cocok untukb(rentang 0..7);15(4 bit) cocok untukc(rentang 0..15). Semua assignment bersih tanpa kehilangan data. - Baris 22 (
f.b = 9;): nilai 9 butuh 4 bit (biner1001) tetapibhanya 3 bit. Compiler menyimpan 3 bit terendah (001= 1) dan membuang bit sisanya. Makaf.bmenjadi 1—bukan 9. Compiler juga memancarkan warning-Woverflowuntuk assignment seperti ini, yang sengaja kita pertahankan untuk mendemonstrasikan perilaku truncation. - Baris 25 (
f.b = 13;): nilai 13 (biner1101) tersimpan sebagai 3 bit terendah (101= 5). Sekali lagi, kelebihan bit dibuang tanpa runtime error—hanya warning saat kompilasi. - Baris 28-30 (
(f.b & 1),(f.b & 2),(f.b & 4)): tes bit individual dengan operasi AND. Untukf.b = 6(biner110), bit terendah (LSB) bernilai 0, bit tengah bernilai 1, bit tertinggi (dari tiga bit) bernilai 1.
Bit-fields untuk Header dan Status Perangkat #
Salah satu penggunaan klasik bit-fields adalah merepresentasikan header protokol jaringan atau status perangkat keras yang tata letaknya sudah ditentukan per bit.
#include <stdio.h>
/* bit-fields sering dipakai untuk merepresentasikan register hardware
atau header protokol jaringan dengan tata letak bit presisi. */
struct HeaderTCP {
unsigned short src_port : 16;
unsigned short dst_port : 16;
unsigned int seq_num : 32;
};
/* Bendera status perangkat */
struct DeviceStatus {
unsigned power_on : 1;
unsigned error : 1;
unsigned standby : 1;
unsigned mode : 2; /* 4 mode: 0-3 */
unsigned reserved : 3;
};
int main(void) {
struct HeaderTCP hdr = {8080, 80, 12345};
printf("TCP Header:\n");
printf(" src_port = %u\n", hdr.src_port);
printf(" dst_port = %u\n", hdr.dst_port);
printf(" seq_num = %u\n", hdr.seq_num);
printf(" size = %zu byte\n", sizeof(struct HeaderTCP));
struct DeviceStatus dev = {0};
dev.power_on = 1;
dev.mode = 3;
dev.error = 0;
dev.standby = 0;
printf("\nDeviceStatus:\n");
printf(" power_on = %u\n", dev.power_on);
printf(" mode = %u\n", dev.mode);
printf(" size = %zu byte\n", sizeof(struct DeviceStatus));
/* Mode hanya 2 bit -> batas 0..3 */
dev.mode = 5; /* 5 = 101 biner, hanya 01 tersimpan = 1 */
printf(" mode setelah = 5 -> tersimpan %u\n", dev.mode);
return 0;
}Output yang Dihasilkan:
TCP Header:
src_port = 8080
dst_port = 80
seq_num = 12345
size = 8 byte
DeviceStatus:
power_on = 1
mode = 3
size = 4 byte
mode setelah = 5 -> tersimpan 1Pada contoh di atas, struct HeaderTCP memodelkan bagian awal header TCP: 16-bit source port, 16-bit destination port, 32-bit sequence number. Total 64 bit atau 8 byte—ukuran yang sama dengan dua unsigned int (atau empat unsigned short), tetapi tata letaknya kini eksplisit per member. struct DeviceStatus memadatkan tujuh flag (1+1+1+2+3 = 8 bit) ke dalam satu unit 4 byte; pada akhir contoh, dev.mode = 5 kembali mendemonstrasikan truncation: 5 (biner 101) tersimpan sebagai 1 (biner 01) karena hanya 2 bit yang tersedia. Compiler memancarkan warning yang sama seperti sebelumnya.