Jawaban:
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:
typedef union
{
struct {
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
} bytes;
unsigned int dword;
} HW_Register;
HW_Register reg;
Maka Anda dapat mengakses reg sebagai berikut:
reg.dword = 0x12345678;
reg.bytes.byte3 = 4;
Endianness (urutan byte) dan arsitektur prosesor tentu saja penting.
Fitur lain yang bermanfaat adalah pengubah bit:
typedef union
{
struct {
unsigned char b1:1;
unsigned char b2:1;
unsigned char b3:1;
unsigned char b4:1;
unsigned char reserved:4;
} bits;
unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;
Dengan kode ini Anda dapat mengakses secara langsung satu bit di register / alamat memori:
x = reg.bits.b2;
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.
typedef union {
unsigned char control_byte;
struct {
unsigned int nibble : 4;
unsigned int nmi : 1;
unsigned int enabled : 1;
unsigned int fired : 1;
unsigned int control : 1;
};
} ControlRegister;
Saya telah melihatnya di beberapa perpustakaan sebagai pengganti warisan berorientasi objek.
Misalnya
Connection
/ | \
Network USB VirtualConnection
Jika Anda ingin Koneksi "kelas" menjadi salah satu dari yang di atas, Anda dapat menulis sesuatu seperti:
struct Connection
{
int type;
union
{
struct Network network;
struct USB usb;
struct Virtual virtual;
}
};
Contoh penggunaan di libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74
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:
union {
int a;
int b;
int c;
} myUnion;
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.
Banyak penggunaan. Lakukan saja grep union /usr/include/*
atau di direktori serupa. Sebagian besar kasus yang union
dibungkus dengan struct
dan salah satu anggota struct memberitahu elemen mana dalam serikat untuk mengakses. Misalnya checkout man elf
untuk implementasi kehidupan nyata.
Ini adalah prinsip dasar:
struct _mydata {
int which_one;
union _data {
int a;
float b;
char c;
} foo;
} bar;
switch (bar.which_one)
{
case INTEGER : /* access bar.foo.a;*/ break;
case FLOATING : /* access bar.foo.b;*/ break;
case CHARACTER: /* access bar.foo.c;*/ break;
}
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:
set a to b times 7.
terdiri dari elemen bahasa berikut:
Elemen bahasa didefinisikan sebagai #define
nilai ' ' sebagai berikut:
#define ELEM_SYM_SET 0
#define ELEM_SYM_TO 1
#define ELEM_SYM_TIMES 2
#define ELEM_SYM_FULLSTOP 3
#define ELEM_VARIABLE 100
#define ELEM_CONSTANT 101
dan struktur berikut digunakan untuk menyimpan setiap elemen:
typedef struct {
int typ;
union {
char *str;
int val;
}
} tElem;
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:
tElem e;
e.typ = ELEM_SYM_SET;
Untuk membuat elemen "variabel [b]", Anda akan menggunakan:
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
Untuk membuat elemen "konstan [7]", Anda akan menggunakan:
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
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
dan val
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 memori 0x1010
dan bilangan bulat dan pointer keduanya 4 byte:
+-----------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-----+-----+
0x1014 | | |
0x1015 | str | val |
0x1016 | | |
0x1017 | | |
+-----+-----+
Jika hanya dalam struktur, itu akan terlihat seperti ini:
+-------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-------+
0x1014 | |
0x1015 | str |
0x1016 | |
0x1017 | |
+-------+
0x1018 | |
0x1019 | val |
0x101A | |
0x101B | |
+-------+
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:
struct variant {
int type;
double number;
char *string;
};
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:
struct variant {
int type;
union {
double number;
char *string;
} value;
};
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.
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:
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnion hanya akan berisi int ATAU float, tergantung pada yang paling baru Anda atur . Jadi melakukan ini:
MYUNION u;
u.MyInt = 10;
kamu sekarang memegang int sama dengan 10;
u.MyFloat = 1.0;
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.
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.
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.
typedef union
{
UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for
// the entire set of fields (including the payload)
struct
{
UINT8 size;
UINT8 cmd;
UINT8 payload[PAYLOAD_SIZE];
UINT8 crc;
} fields;
}PACKET_T;
// This should be called every time a new byte of data is ready
// and point to the packet's buffer:
// packet_builder(packet.buffer, new_data);
void packet_builder(UINT8* buffer, UINT8 data)
{
static UINT8 received_bytes = 0;
// All range checking etc removed for brevity
buffer[received_bytes] = data;
received_bytes++;
// Using the struc only way adds lots of logic that relates "byte 0" to size
// "byte 1" to cmd, etc...
}
void packet_handler(PACKET_T* packet)
{
// Process the fields in a readable manner
if(packet->fields.size > TOO_BIG)
{
// handle error...
}
if(packet->fields.cmd == CMD_X)
{
// do stuff..
}
}
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.
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.
acara serikat { eventCode panjang yang tidak ditandatangani; unsigned char eventParts [4]; };
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".
Di sekolah, saya menggunakan serikat pekerja seperti ini:
typedef union
{
unsigned char color[4];
int new_color;
} u_color;
Saya menggunakannya untuk menangani warna lebih mudah, daripada menggunakan >> dan << operator, saya hanya harus melalui indeks yang berbeda dari array char saya.
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:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
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:
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
struct
sekitar higher
/ 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.
Dalam versi awal C, semua deklarasi struktur akan berbagi set bidang yang sama. Diberikan:
struct x {int x_mode; int q; float x_f};
struct y {int y_mode; int q; int y_l};
struct z {int z_mode; char name[20];};
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
dari struct x
dan struct 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 ke U*
setara dengan casting itu ke pointer ke setiap jenis serikat yang mengandung keduanya T
dan U
, dan kemudian casting itu untuk U*
, 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 dari T
hingga U
. Lebih lanjut, dalam kasus di mana fungsi menerima pointer dari asal tidak diketahui, perilaku menulis objek melalui T*
, mengubahT*
ke a U*
, dan kemudian membaca objek melalui U*
akan sama dengan menulis serikat melalui anggota tipe T
dan membaca sebagai tipe U
, 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.
foo
adalah int
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 apakah anyPointer
diidentifikasi semua tipe struktur yang telah foo
terdaftar di antara anggotanya
anyPointer
indentifikasi dengan anggota struct, lalu bagaimana kompiler akan memeriksa kondisi to have a member with the same name only if the type and offset matched
posting Anda?
p->foo
tergantung pada jenis dan offset foo
. 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:
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
secara teknis ini masih merupakan pelanggaran aturan. tetapi semua standar yang dikenal mendukung penggunaan ini.