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.

c
#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:

text
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:

  1. Baris 6-10 (union Data { int i; float f; char s[8]; };): tiga member berbagi satu blok memori. Ukuran union sama dengan ukuran member terbesar, yaitu char[8] = 8 byte. Meskipun int dan float masing-masing hanya 4 byte, blok tetap 8 byte untuk menampung s.
  2. Baris 13 (union Data d;): deklarasi variabel d. Memori 8 byte dialokasikan; semua member i, f, s menunjuk ke alamat awal yang sama.
  3. Baris 19-22 (d.i = 65; printf d.i, d.f, d.s): setelah menulis d.i = 65, kita membaca kembali ketiga member. d.i valid bernilai 65. d.f membaca bit yang sama sebagai float—hasilnya 9.10844e-44, angka hampir nol karena pola bit int 65 tidak merepresentasikan float yang bermakna. d.s membaca byte pertama (yang bernilai 65, yaitu karakter ASCII 'A'), lalu byte nol terminator sehingga tercetak "A".
  4. Baris 26-29 (d.f = 3.14f; printf d.i, d.f): setelah menulis d.f = 3.14f, bit di blok kini berisi representasi float 3.14. Membaca d.i menampilkan 1078523331—angka yang tidak bermakna bagi kita tetapi merupakan interpretasi 4 byte pertama sebagai int. Membaca d.f valid kembali bernilai 3.14.
  5. Baris 33-36 (strcpy(d.s, "Hi!")): menulis string "Hi!" ke blok. Byte pertama menjadi 'H' (72), kedua 'i' (105), ketiga '!' (33), keempat '\0'. Membaca d.i menghasilkan 2189640—interpretasi 4 byte pertama sebagai int. Membaca d.s valid 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.

c
#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:

text
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.