union memesan satu ruang memori yang dipakai bergantian oleh beberapa anggota. Berbeda dari struct yang menyimpan seluruh anggotanya sekaligus, union hanya menampung satu anggota aktif pada satu saat dan ukurannya mengikuti anggota terbesarnya. Topik ini membahas definisi dan pemakaian union, perilaku berbagi memorinya, serta pola union bertag yang menyimpan penanda jenis data bersama nilainya.
Mendefinisikan dan memakai union#
Bentuk definisi union sama dengan struct, yaitu kata kunci union, tag, dan daftar anggota di dalam kurung kurawal. Anggotanya dideklarasikan seperti anggota struct biasa, tetapi semuanya berawal dari alamat yang sama. Menulis ke satu anggota menimpa isi anggota lain, sehingga pembacaan yang benar hanya dilakukan terhadap anggota terakhir yang ditulis. Nilai awal yang diberikan dengan kurung kurawal mengisi anggota pertamanya, sehingga pada union Nilai di bawah penulisan union Nilai n = {65}; mengisi angka dan bukan huruf.
Program berikut membandingkan ukuran union dengan struct yang anggotanya sama, lalu memperlihatkan akibat penulisan anggota yang bertumpuk.
#include <stdio.h>
union Nilai {
int angka;
char huruf;
};
struct Pasangan {
int angka;
char huruf;
};
int main(void) {
union Nilai n;
printf("sizeof(union Nilai) = %zu\n", sizeof(union Nilai));
printf("sizeof(struct Pasangan) = %zu\n", sizeof(struct Pasangan));
n.angka = 12345678;
printf("Setelah n.angka = 12345678, n.angka = %d\n", n.angka);
n.huruf = 'A';
printf("Setelah n.huruf = 'A', n.huruf = %c\n", n.huruf);
printf("n.angka kini terbaca = %d\n", n.angka);
return 0;
}sizeof(union Nilai) = 4
sizeof(struct Pasangan) = 8
Setelah n.angka = 12345678, n.angka = 12345678
Setelah n.huruf = 'A', n.huruf = A
n.angka kini terbaca = 12345665union Nilai menampung int dan char dalam 4 byte, sedangkan struct dengan anggota yang sama memerlukan 8 byte. Ukuran union ditentukan oleh anggota terbesarnya, ditambah padding apabila perataannya menuntut. Setelah n.huruf diisi 'A', pembacaan n.angka menghasilkan 12345665 dan bukan 12345678 karena satu byte pertama dari int itu tertimpa. Pembacaan n.huruf tetap normal karena penulisan 'A' menyentuh tepat byte yang dibacanya. Nilai hasil pembacaan anggota yang tidak aktif bergantung pada platform, sehingga contoh ini hanya untuk memperlihatkan berbagi memori dan bukan pola yang ditiru pada program nyata.
Program berikut menegaskan aturan ukuran tersebut dengan union yang anggotanya lebih beragam.
#include <stdio.h>
union Ukuran {
char c;
int i;
double d;
};
int main(void) {
printf("sizeof(char) = %zu\n", sizeof(char));
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(double) = %zu\n", sizeof(double));
printf("sizeof(union Ukuran) = %zu\n", sizeof(union Ukuran));
return 0;
}sizeof(char) = 1
sizeof(int) = 4
sizeof(double) = 8
sizeof(union Ukuran) = 8Anggota terbesar union di atas adalah double berukuran 8 byte, dan ukuran union mengikutinya secara penuh meskipun dua anggota lainnya jauh lebih kecil. Aturan ini membuat ukuran union mudah diduga sebelum diukur, yaitu ukuran anggota terbesarnya, sedangkan ukuran struct justru perlu memperhitungkan padding di antara anggotanya. Karena itu dua union dengan anggota berbeda dapat saja berukuran sama selama anggota terbesarnya sama.
Pola union bertag#
Union tidak mencatat anggota mana yang terakhir ditulis, sehingga informasi itu perlu disimpan secara terpisah. Pola yang lazim menyandingkan penanda jenis bersama union di dalam satu struct. Penandanya berupa enum, yaitu kumpulan konstanta bernama, dan pembacaan union hanya dilakukan setelah penanda diperiksa. Dengan susunan itu, satu variabel dapat menampung salah satu dari beberapa jenis data tanpa memperbesar ukurannya, karena yang bertambah hanyalah ruang penandanya.
#include <stdio.h>
enum Jenis {
PERSEGI,
LINGKARAN
};
struct Pengukuran {
enum Jenis jenis;
union {
double sisi;
double radius;
} ukur;
};
double luas(struct Pengukuran p) {
if (p.jenis == PERSEGI) {
return p.ukur.sisi * p.ukur.sisi;
}
return 3.141592653589793 * p.ukur.radius * p.ukur.radius;
}
int main(void) {
struct Pengukuran halaman;
struct Pengukuran kolam;
halaman.jenis = PERSEGI;
halaman.ukur.sisi = 7.5;
printf("Luas halaman : %.2f\n", luas(halaman));
kolam.jenis = LINGKARAN;
kolam.ukur.radius = 2.0;
printf("Luas kolam : %.2f\n", luas(kolam));
return 0;
}Luas halaman : 56.25
Luas kolam : 12.57Struct Pengukuran menyimpan jenis dan union ukur sebagai satu kesatuan. Fungsi luas memeriksa jenis terlebih dahulu, lalu memilih anggota union yang sesuai sebelum menghitungnya. Pola ini menjamin hanya anggota aktif yang dibaca, sehingga nilai yang keluar selalu sah. Satu struct pun cukup menampung kedua bentuk pengukuran karena kedua anggota union berbagi memori yang sama.
Urutan kerja pola ini dapat dirangkum menjadi tiga langkah yang selalu sama, yaitu menuliskan penanda sebelum menulis union, memeriksa penanda sebelum membaca, dan hanya membaca anggota yang ditunjuk penandanya. Langkah pemeriksaan sebaiknya ditempatkan di satu fungsi, sebagaimana luas di atas, agar aturan itu ditegakkan di satu tempat dan tidak tersebar di setiap pemakaian. Pola bertag semacam ini menjadi cara C mewakili data yang bentuknya berpindah-pindah, misalnya satu baris masukan yang isinya bisa berupa bilangan atau teks.
Penghematan memorinya dapat diukur pada platform yang sama. Struct Pengukuran di atas berukuran 16 byte pada gcc Linux x86-64, sedangkan struct serupa yang menampung sisi dan radius sekaligus tanpa union berukuran 24 byte. Selisih 8 byte per data terasa besar pada array berisi ribuan pengukuran, dan keunggulan itu diperoleh tanpa kehilangan kejelasan jenis datanya.
Kesalahan umum#
Membaca anggota yang bukan tulisan terakhir merupakan kekeliruan yang paling umum pada union. Programnya tetap berjalan tanpa pesan error, tetapi nilainya ditafsirkan ulang dari byte yang tidak sesuai, sebagaimana perubahan 12345678 menjadi 12345665 pada program di atas. Menyimpan jenis datanya dalam penanda dan memeriksanya sebelum membaca menutup celah ini sepenuhnya.
Kekeliruan kedua adalah menduga ukuran union sama dengan penjumlahan ukuran anggotanya. Ukuran union mengikuti anggota terbesarnya, sehingga sizeof terhadap tipenya tetap menjadi ukuran yang benar saat menghitung array atau memesan memori.
Kekeliruan ketiga adalah menulis union tanpa penanda jenisnya. Program tetap berhasil dikompilasi dan berjalan, tetapi tidak ada satu pun cara untuk mengetahui anggota mana yang terakhir diisi, sehingga pembacaannya menjadi tebakan. Penanda bertipe enum yang menyertai union merupakan bagian dari datanya, dan bukan pelengkap yang boleh ditinggalkan.
Ringkasan#
- Union menempatkan seluruh anggotanya pada alamat yang sama, sehingga hanya satu anggota yang sah pada satu saat.
- Ukuran union mengikuti anggota terbesarnya, dan ukuran itu diukur dengan sizeof terhadap tipenya.
- Menulis satu anggota menimpa isi anggota lain, dan membaca anggota yang tidak aktif memberikan nilai yang bergantung platform.
- Pola union bertag menyimpan penanda jenis dalam enum bersama union, sehingga pembacaan selalu mengenai anggota yang benar.
Referensi#
- Union declaration — Cppreference — Kaidah definisi union dan perilaku berbagi memorinya.
- enum specifier — Cppreference — Kaidah deklarasi konstanta bernama yang dipakai sebagai penanda jenis.