Sebuah struct mengalokasikan ruang terpisah untuk setiap member—member int dan float berada di alamat berbeda. Berbeda dengan struct, sebuah union mengalokasikan satu blok memori yang dipakai bersama oleh semua member. Akibatnya, hanya satu member yang valid pada satu waktu; menulis ke member lain akan menimpa nilai sebelumnya. union cocok untuk situasi ketika sebuah nilai bisa berupa salah satu dari beberapa tipe: angka yang bisa berupa int atau float, atau data yang bisa berupa angka atau string. Pada chapter ini kita akan mendefinisikan union, mengamati ukurannya, dan memahami konsekuensi berbagi memori.
Apa Itu union #
union adalah tipe agregat seperti struct, tetapi semua member-nya berbagi alamat awal yang sama. Ukuran union sama dengan ukuran member terbesar (ditambah padding untuk alignment). Pada satu saat, hanya satu member yang menyimpan nilai valid—anggota lain membaca byte yang sama dengan interpretasi tipe yang berbeda, sehingga hasilnya umumnya tidak bermakna kecuali kita tahu member mana yang terakhir ditulis.
Mekanisme kerja di balik layar: ketika kita mendeklarasikan union Data { int i; float f; char s[8]; };, compiler mengalokasikan blok memori sebesar max(sizeof(int), sizeof(float), sizeof(char[8])) = 8 byte. Member i, f, dan s semua menunjuk ke alamat awal blok tersebut. Menulis d.i = 65; menempatkan pola bit int 65 ke dalam blok. Jika kita membaca d.f setelah itu, compiler menerjemahkan bit yang sama sebagai float—hasilnya biasanya angka kecil tak bermakna karena pola bit int 65 bukan representasi float yang masuk akal.
#include <stdio.h>
#include <string.h>
/* union Data: anggota berbagi memori yang sama */
union Data {
int i;
float f;
char s[8];
};
int main(void) {
union Data d;
printf("sizeof(union Data) = %zu byte\n", sizeof(union Data));
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(float) = %zu\n", sizeof(float));
printf("sizeof(char[8]) = %zu\n", sizeof(char[8]));
/* Tulis sebagai int, baca sebagai int */
d.i = 65;
printf("\nSetelah d.i = 65:\n");
printf(" d.i = %d\n", d.i);
printf(" d.f = %g (interpretasi float dari bit yang sama)\n", d.f);
printf(" d.s = %s (interpretasi string)\n", d.s);
/* Tulis sebagai float, baca kembali */
d.f = 3.14f;
printf("\nSetelah d.f = 3.14:\n");
printf(" d.i = %d (kini rusak, float menimpa int)\n", d.i);
printf(" d.f = %g\n", d.f);
/* Tulis sebagai string */
strcpy(d.s, "Hi!");
printf("\nSetelah d.s = \"Hi!\":\n");
printf(" d.i = %d (kini rusak)\n", d.i);
printf(" d.s = %s\n", d.s);
return 0;
}Output yang Dihasilkan:
sizeof(union Data) = 8 byte
sizeof(int) = 4
sizeof(float) = 4
sizeof(char[8]) = 8
Setelah d.i = 65:
d.i = 65
d.f = 9.10844e-44 (interpretasi float dari bit yang sama)
d.s = A (interpretasi string)
Setelah d.f = 3.14:
d.i = 1078523331 (kini rusak, float menimpa int)
d.f = 3.14
Setelah d.s = "Hi!":
d.i = 2189640 (kini rusak)
d.s = Hi!Mari kita telaah baris-baris kode di atas:
- Baris 6-10 (
union Data { int i; float f; char s[8]; };): tiga member berbagi satu blok memori. Ukuranunionsama dengan ukuran member terbesar, yaituchar[8]= 8 byte. Meskipunintdanfloatmasing-masing hanya 4 byte, blok tetap 8 byte untuk menampungs. - Baris 13 (
union Data d;): deklarasi variabeld. Memori 8 byte dialokasikan; semua memberi,f,smenunjuk ke alamat awal yang sama. - Baris 19-22 (
d.i = 65; printf d.i, d.f, d.s): setelah menulisd.i = 65, kita membaca kembali ketiga member.d.ivalid bernilai 65.d.fmembaca bit yang sama sebagaifloat—hasilnya9.10844e-44, angka hampir nol karena pola bitint65 tidak merepresentasikanfloatyang bermakna.d.smembaca byte pertama (yang bernilai 65, yaitu karakter ASCII'A'), lalu byte nol terminator sehingga tercetak"A". - Baris 26-29 (
d.f = 3.14f; printf d.i, d.f): setelah menulisd.f = 3.14f, bit di blok kini berisi representasifloat3.14. Membacad.imenampilkan1078523331—angka yang tidak bermakna bagi kita tetapi merupakan interpretasi 4 byte pertama sebagaiint. Membacad.fvalid kembali bernilai 3.14. - Baris 33-36 (
strcpy(d.s, "Hi!")): menulis string"Hi!"ke blok. Byte pertama menjadi'H'(72), kedua'i'(105), ketiga'!'(33), keempat'\0'. Membacad.imenghasilkan2189640—interpretasi 4 byte pertama sebagaiint. Membacad.svalid kembali bernilai"Hi!".
union sebagai Bagian struct untuk Tipe Varian #
union paling berguna ketika dipasangkan dengan struct dan sebuah tag (biasanya enum) yang mencatat member mana yang sedang aktif. Pola ini disebut tagged union dan akan kita bahas lebih mendalam di chapter tersendiri. Berikut sekilas pratinjau polanya.
#include <stdio.h>
#include <string.h>
/* union untuk merepresentasikan nilai yang bisa salah satu dari beberapa tipe */
typedef enum {
KIND_INT,
KIND_FLOAT,
KIND_STR
} Kind;
typedef union {
int as_int;
float as_float;
char as_str[32];
} Value;
typedef struct {
Kind kind;
Value value;
} Variant;
void tampilkan(const Variant *v) {
switch (v->kind) {
case KIND_INT:
printf("INT : %d\n", v->value.as_int);
break;
case KIND_FLOAT:
printf("FLOAT: %f\n", v->value.as_float);
break;
case KIND_STR:
printf("STR : %s\n", v->value.as_str);
break;
}
}
int main(void) {
Variant a, b, c;
a.kind = KIND_INT;
a.value.as_int = 42;
b.kind = KIND_FLOAT;
b.value.as_float = 3.14f;
c.kind = KIND_STR;
strncpy(c.value.as_str, "Halo union", sizeof(c.value.as_str) - 1);
c.value.as_str[sizeof(c.value.as_str) - 1] = '\0';
tampilkan(&a);
tampilkan(&b);
tampilkan(&c);
printf("\nsizeof(Variant) = %zu byte\n", sizeof(Variant));
printf(" (Kind = %zu + Value = %zu)\n",
sizeof(Kind), sizeof(Value));
return 0;
}Output yang Dihasilkan:
INT : 42
FLOAT: 3.140000
STR : Halo union
sizeof(Variant) = 36 byte
(Kind = 4 + Value = 32)Pada contoh di atas, struct Variant menggabungkan tag kind (bertipe enum Kind) dengan union Value. Field kind mencatat tipe mana yang sedang aktif, sehingga fungsi tampilkan tahu member mana yang harus dibaca. Tanpa tag, pembaca tidak punya cara untuk mengetahui apakah angka 42 di as_int adalah int yang dimaksud atau sampah dari penulisan as_float sebelumnya. Ukuran Variant adalah 36 byte: Kind 4 byte + Value 32 byte, dengan beberapa byte padding untuk alignment Value yang terbesar 4.