Kapan serikat harus digunakan? mengapa kita membutuhkan mereka?
236
Serikat pekerja sering digunakan untuk mengkonversi antara representasi biner dari bilangan bulat dan mengapung:
union
{
int i;
float f;
} u;
// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);
Meskipun perilaku ini secara teknis tidak terdefinisi menurut standar C (Anda seharusnya membaca bidang yang baru saja ditulis), ini akan bertindak dengan cara yang terdefinisi dengan baik di hampir semua kompiler.
Serikat pekerja kadang-kadang juga digunakan untuk mengimplementasikan pseudo-polimorfisme dalam C, dengan memberikan struktur beberapa tag yang menunjukkan jenis objek apa yang dikandungnya, dan kemudian menyatukan jenis-jenis yang mungkin bersama:
enum Type { INTS, FLOATS, DOUBLE };
struct S
{
Type s_type;
union
{
int s_ints[2];
float s_floats[2];
double s_double;
};
};
void do_something(struct S *s)
{
switch(s->s_type)
{
case INTS: // do something with s->s_ints
break;
case FLOATS: // do something with s->s_floats
break;
case DOUBLE: // do something with s->s_double
break;
}
}
Ini memungkinkan ukuran struct S
hanya 12 byte, bukan 28.
Serikat pekerja sangat berguna dalam pemrograman Tertanam atau dalam situasi di mana akses langsung ke perangkat keras / memori diperlukan. Ini adalah contoh sepele:
Maka Anda dapat mengakses reg sebagai berikut:
Endianness (urutan byte) dan arsitektur prosesor tentu saja penting.
Fitur lain yang bermanfaat adalah pengubah bit:
Dengan kode ini Anda dapat mengakses secara langsung satu bit di register / alamat memori:
sumber
Pemrograman sistem tingkat rendah adalah contoh yang masuk akal.
IIRC, saya telah menggunakan serikat untuk memecah register perangkat keras ke dalam bit komponen. Jadi, Anda dapat mengakses register 8-bit (seperti sebelumnya, pada hari saya melakukan ini ;-) ke dalam bit komponen.
(Saya lupa sintaks yang tepat tapi ...) Struktur ini akan memungkinkan register kontrol untuk diakses sebagai control_byte atau melalui bit individu. Penting untuk memastikan bit memetakan ke bit register yang benar untuk endianness yang diberikan.
sumber
Saya telah melihatnya di beberapa perpustakaan sebagai pengganti warisan berorientasi objek.
Misalnya
Jika Anda ingin Koneksi "kelas" menjadi salah satu dari yang di atas, Anda dapat menulis sesuatu seperti:
Contoh penggunaan di libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74
sumber
Serikat pekerja memungkinkan anggota data yang saling eksklusif untuk berbagi memori yang sama. Ini cukup penting ketika memori lebih langka, seperti pada sistem embedded.
Dalam contoh berikut:
Serikat ini akan mengambil ruang int tunggal, daripada 3 nilai int terpisah. Jika pengguna mengatur nilai a , dan kemudian mengatur nilai b , itu akan menimpa nilai a karena mereka berdua berbagi lokasi memori yang sama.
sumber
Banyak penggunaan. Lakukan saja
grep union /usr/include/*
atau di direktori serupa. Sebagian besar kasus yangunion
dibungkus denganstruct
dan salah satu anggota struct memberitahu elemen mana dalam serikat untuk mengakses. Misalnya checkoutman elf
untuk implementasi kehidupan nyata.Ini adalah prinsip dasar:
sumber
Berikut adalah contoh penyatuan dari basis kode saya sendiri (dari memori dan diparafrasekan sehingga mungkin tidak tepat). Itu digunakan untuk menyimpan elemen bahasa dalam juru bahasa yang saya buat. Misalnya, kode berikut:
terdiri dari elemen bahasa berikut:
Elemen bahasa didefinisikan sebagai
#define
nilai ' ' sebagai berikut:dan struktur berikut digunakan untuk menyimpan setiap elemen:
maka ukuran setiap elemen adalah ukuran serikat maksimum (4 byte untuk typ dan 4 byte untuk union, meskipun itu adalah nilai khas, ukuran sebenarnya tergantung pada implementasi).
Untuk membuat elemen "set", Anda akan menggunakan:
Untuk membuat elemen "variabel [b]", Anda akan menggunakan:
Untuk membuat elemen "konstan [7]", Anda akan menggunakan:
dan Anda dapat dengan mudah mengembangkannya untuk menyertakan float (
float flt
) atau rasional (struct ratnl {int num; int denom;}
) dan tipe lainnya.Premis dasarnya adalah bahwa
str
danval
tidak bersebelahan dalam memori, mereka sebenarnya tumpang tindih, jadi ini adalah cara untuk mendapatkan pandangan yang berbeda pada blok memori yang sama, diilustrasikan di sini, di mana struktur didasarkan pada lokasi memori0x1010
dan bilangan bulat dan pointer keduanya 4 byte:Jika hanya dalam struktur, itu akan terlihat seperti ini:
sumber
make sure you free this later
komentar dihapus dari elemen konstan?Saya akan mengatakan itu membuatnya lebih mudah untuk menggunakan kembali memori yang mungkin digunakan dengan cara yang berbeda, yaitu menghemat memori. Misalnya Anda ingin melakukan beberapa "varian" struct yang dapat menyimpan string pendek serta nomor:
Dalam sistem 32 bit ini akan menghasilkan setidaknya 96 bit atau 12 byte yang digunakan untuk setiap instance dari
variant
.Menggunakan gabungan Anda dapat mengurangi ukurannya menjadi 64 bit atau 8 byte:
Anda dapat menyimpan lebih banyak lagi jika Anda ingin menambahkan lebih banyak tipe variabel yang lain, dll. Mungkin benar, bahwa Anda dapat melakukan hal serupa dengan menggunakan penunjuk kosong - tetapi serikat membuatnya lebih mudah diakses serta mengetik aman. Penghematan seperti itu tidak terdengar masif, tetapi Anda menghemat sepertiga dari memori yang digunakan untuk semua instance struct ini.
sumber
Sulit untuk memikirkan peristiwa tertentu ketika Anda membutuhkan jenis struktur fleksibel ini, mungkin dalam protokol pesan di mana Anda akan mengirim berbagai ukuran pesan, tetapi meskipun demikian mungkin ada alternatif yang lebih baik dan lebih ramah bagi programmer.
Serikat pekerja agak mirip jenis varian dalam bahasa lain - mereka hanya bisa menampung satu hal pada satu waktu, tetapi benda itu bisa berupa int, float, dll. Tergantung pada cara Anda mendeklarasikannya.
Sebagai contoh:
MyUnion hanya akan berisi int ATAU float, tergantung pada yang paling baru Anda atur . Jadi melakukan ini:
kamu sekarang memegang int sama dengan 10;
kamu sekarang memegang float sama dengan 1.0. Itu tidak lagi memiliki int. Jelas sekarang jika Anda mencoba dan melakukan printf ("MyInt =% d", u.MyInt); maka Anda mungkin akan mendapatkan kesalahan, meskipun saya tidak yakin dengan perilaku tertentu.
Ukuran serikat ditentukan oleh ukuran bidang terbesarnya, dalam hal ini pelampung.
sumber
sizeof(int) == sizeof(float)
(== 32
) biasanya.Serikat pekerja digunakan ketika Anda ingin memodelkan struct yang ditentukan oleh perangkat keras, perangkat atau protokol jaringan, atau ketika Anda membuat sejumlah besar objek dan ingin menghemat ruang. Anda benar-benar tidak membutuhkannya 95% dari waktu, tetap dengan kode debug yang mudah.
sumber
Banyak dari jawaban ini berhubungan dengan casting dari satu tipe ke tipe lainnya. Saya mendapatkan yang paling banyak digunakan dari serikat dengan jenis yang sama hanya lebih dari mereka (yaitu ketika mengurai aliran data serial). Mereka memungkinkan parsing / konstruksi paket berbingkai menjadi sepele.
Sunting Komentar tentang endianness dan struct padding adalah valid, dan bagus, perhatian. Saya telah menggunakan kode tubuh ini hampir seluruhnya dalam perangkat lunak tertanam, yang sebagian besar saya kendalikan kedua ujung pipa.
sumber
Serikat pekerja itu hebat. Salah satu penggunaan cerdas serikat pekerja yang pernah saya lihat adalah menggunakannya saat mendefinisikan suatu peristiwa. Misalnya, Anda mungkin memutuskan bahwa suatu peristiwa adalah 32 bit.
Sekarang, dalam 32 bit itu, Anda mungkin ingin menetapkan 8 bit pertama sebagai pengidentifikasi pengirim acara ... Kadang-kadang Anda berurusan dengan acara secara keseluruhan, kadang-kadang Anda membedahnya dan membandingkan komponen-komponennya. serikat memberi Anda fleksibilitas untuk melakukan keduanya.
sumber
Bagaimana dengan
VARIANT
yang digunakan dalam antarmuka COM? Ini memiliki dua bidang - "tipe" dan serikat yang memegang nilai aktual yang diperlakukan tergantung pada bidang "tipe".sumber
Di sekolah, saya menggunakan serikat pekerja seperti ini:
Saya menggunakannya untuk menangani warna lebih mudah, daripada menggunakan >> dan << operator, saya hanya harus melalui indeks yang berbeda dari array char saya.
sumber
Saya menggunakan penyatuan saat saya mengkode untuk perangkat yang disematkan. Saya memiliki C int yang panjangnya 16 bit. Dan saya harus mengambil 8 bit yang lebih tinggi dan 8 bit yang lebih rendah ketika saya perlu membaca dari / store ke EEPROM. Jadi saya menggunakan cara ini:
Tidak perlu digeser agar kode lebih mudah dibaca.
Di sisi lain, saya melihat beberapa kode C ++ stl lama yang menggunakan union untuk stl pengalokasi. Jika Anda tertarik, Anda dapat membaca kode sumber sgi stl . Ini adalah bagiannya:
sumber
struct
sekitarhigher
/lower
? Saat ini keduanya harus menunjuk ke byte pertama saja.Lihatlah ini: X.25 penanganan perintah buffer
Salah satu dari banyak perintah X.25 yang mungkin diterima ke dalam buffer dan ditangani dengan menggunakan UNION dari semua struktur yang mungkin.
sumber
Dalam versi awal C, semua deklarasi struktur akan berbagi set bidang yang sama. Diberikan:
kompiler pada dasarnya akan menghasilkan tabel ukuran struktur (dan mungkin keberpihakan), dan tabel terpisah dari nama, tipe, dan offset anggota struktur. Kompiler tidak melacak anggota mana yang termasuk dalam struktur mana, dan akan memungkinkan dua struktur memiliki anggota dengan nama yang sama hanya jika jenis dan offsetnya cocok (seperti dengan anggota
q
daristruct x
danstruct y
). Jika p adalah pointer ke tipe struktur apa pun, p-> q akan menambahkan offset "q" ke pointer p dan mengambil "int" dari alamat yang dihasilkan.Mengingat semantik di atas, adalah mungkin untuk menulis fungsi yang dapat melakukan beberapa operasi yang bermanfaat pada berbagai jenis struktur secara bergantian, asalkan semua bidang yang digunakan oleh fungsi tersebut berbaris dengan bidang yang berguna dalam struktur yang dimaksud. Ini adalah fitur yang berguna, dan mengubah C untuk memvalidasi anggota yang digunakan untuk akses struktur terhadap jenis struktur yang dimaksud akan berarti kehilangannya dengan tidak adanya sarana memiliki struktur yang dapat berisi beberapa bidang bernama pada alamat yang sama. Menambahkan tipe "union" ke C membantu mengisi celah itu (meskipun tidak, IMHO, dan memang seharusnya demikian).
Bagian penting dari kemampuan serikat untuk mengisi celah itu adalah fakta bahwa penunjuk ke anggota serikat dapat dikonversi menjadi penunjuk ke serikat mana pun yang mengandung anggota itu, dan penunjuk ke serikat mana pun dapat dikonversi menjadi penunjuk ke anggota mana pun. Sementara Standar C89 tidak secara tegas mengatakan bahwa casting
T*
langsung keU*
setara dengan casting itu ke pointer ke setiap jenis serikat yang mengandung keduanyaT
danU
, dan kemudian casting itu untukU*
, tidak ada perilaku yang pasti dari urutan pemain terakhir yang akan dipengaruhi oleh jenis serikat yang digunakan, dan Standar tidak menentukan semantik yang bertentangan untuk pemeran langsung dariT
hinggaU
. Lebih lanjut, dalam kasus di mana fungsi menerima pointer dari asal tidak diketahui, perilaku menulis objek melaluiT*
, mengubahT*
ke aU*
, dan kemudian membaca objek melaluiU*
akan sama dengan menulis serikat melalui anggota tipeT
dan membaca sebagai tipeU
, yang akan didefinisikan secara standar dalam beberapa kasus (misalnya ketika mengakses anggota Urutan Awal Umum) dan Implementasi-Ditentukan (lebih tepatnya dari Undefined) untuk sisanya. Walaupun jarang ada program untuk mengeksploitasi jaminan CIS dengan objek aktual dari jenis serikat, jauh lebih umum untuk mengeksploitasi fakta bahwa penunjuk ke objek yang tidak diketahui asalnya harus berperilaku seperti penunjuk bagi anggota serikat dan memiliki jaminan perilaku yang terkait dengannya.sumber
foo
adalahint
dengan offset 8,anyPointer->foo = 1234;
berarti "mengambil alamat di anyPointer, memindahkannya dengan 8 byte, dan melakukan penyimpanan integer dari nilai 1234 ke alamat yang dihasilkan. Kompilator tidak perlu tahu atau peduli apakahanyPointer
diidentifikasi semua tipe struktur yang telahfoo
terdaftar di antara anggotanyaanyPointer
indentifikasi dengan anggota struct, lalu bagaimana kompiler akan memeriksa kondisito have a member with the same name only if the type and offset matched
posting Anda?p->foo
tergantung pada jenis dan offsetfoo
. Intinya,p->foo
adalah singkatan*(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
. Adapun pertanyaan terakhir Anda, ketika seorang kompiler menemukan definisi anggota struct, itu mensyaratkan bahwa tidak ada anggota dengan nama itu ada, atau bahwa anggota dengan nama itu memiliki jenis dan offset yang sama; Saya akan menebak bahwa akan berkotek jika definisi anggota struct yang tidak cocok ada, tapi saya tidak tahu bagaimana menangani kesalahan.Contoh sederhana dan sangat berguna adalah ....
Membayangkan:
Anda memiliki
uint32_t array[2]
dan ingin mengakses Byte ke-3 dan ke-4 dari rantai Byte. Anda bisa melakukannya*((uint16_t*) &array[1])
. Tapi ini sayangnya melanggar aturan aliasing yang ketat!Tetapi kompiler yang dikenal memungkinkan Anda untuk melakukan hal berikut:
secara teknis ini masih merupakan pelanggaran aturan. tetapi semua standar yang dikenal mendukung penggunaan ini.
sumber