Yanıtlar:
Sendikalar genellikle tamsayıların ve şamandıraların ikili gösterimleri arasında dönüştürme yapmak için kullanılır:
union
{
int i;
float f;
} u;
// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);
Bu, C standardına göre teknik olarak tanımlanmamış bir davranış olsa da (yalnızca en son yazılan alanı okumalısınız), hemen hemen her derleyicide iyi tanımlanmış bir şekilde hareket edecektir.
Sendikalar bazen C'de psödo-polimorfizmi uygulamak için, ne tür bir nesne içerdiğini gösteren bir yapı vererek ve sonra olası türleri bir araya getirerek kullanılır:
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;
}
}
Bu, boyutun struct S
28 yerine yalnızca 12 bayt olmasını sağlar .
Sendikalar Gömülü programlamada veya donanıma / belleğe doğrudan erişimin gerekli olduğu durumlarda özellikle kullanışlıdır. İşte önemsiz bir örnek:
typedef union
{
struct {
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
} bytes;
unsigned int dword;
} HW_Register;
HW_Register reg;
Sonra reg'e aşağıdaki gibi erişebilirsiniz:
reg.dword = 0x12345678;
reg.bytes.byte3 = 4;
Endianness (bayt sırası) ve işlemci mimarisi elbette önemlidir.
Bir diğer kullanışlı özellik bit değiştiricidir:
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;
Bu kodla, kayıt / bellek adresindeki tek bir bite doğrudan erişebilirsiniz:
x = reg.bits.b2;
Düşük seviyeli sistem programlama makul bir örnektir.
IIRC, donanım kayıtlarını bileşen bitlerine ayırmak için sendikalar kullandım. Yani, 8 bitlik bir kayda erişebilirsiniz (olduğu gibi, bunu yaptığım gün içinde ;-) bileşen bitlerine.
(Tam sözdizimini unutuyorum ama ...) Bu yapı, bir kontrol kaydına bir kontrol_baytı olarak ya da ayrı bitler aracılığıyla erişilmesine izin verecektir. Belirli bir endianite için bitlerin doğru kayıt bitleriyle eşleşmesini sağlamak önemlidir.
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;
Birkaç kütüphanede nesne yönelmiş mirasın yerini aldım.
Örneğin
Connection
/ | \
Network USB VirtualConnection
Connection "sınıfının" yukarıdakilerden biri olmasını istiyorsanız, şöyle bir şey yazabilirsiniz:
struct Connection
{
int type;
union
{
struct Network network;
struct USB usb;
struct Virtual virtual;
}
};
Libinfinity ile örnek kullanım: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74
Sendikalar, birbirini dışlayan veri üyelerinin aynı belleği paylaşmasına izin verir. Bu, gömülü sistemlerde olduğu gibi bellek daha az olduğunda oldukça önemlidir.
Aşağıdaki örnekte:
union {
int a;
int b;
int c;
} myUnion;
Bu birleşim 3 ayrı int değerinden ziyade tek bir int alanının yerini alacaktır. Kullanıcı a değerini ve sonra b değerini ayarlarsa, her ikisi de aynı bellek konumunu paylaştığı için a değerinin üzerine yazar .
Birçok kullanım. Sadece grep union /usr/include/*
veya benzer dizinlerde yapın. Vakaların çoğu yapının union
bir struct
ve bir üyesine sarılır , birlikte hangi öğeye erişileceğini söyler. Örneğin man elf
, gerçek hayat uygulamaları için ödeme .
Temel prensip budur:
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;
}
İşte benim kendi kod temeli bir sendika örneği (bellekten ve başka bir deyişle tam olmayabilir). Dil öğelerini oluşturduğum bir tercümanda depolamak için kullanıldı. Örneğin, aşağıdaki kod:
set a to b times 7.
aşağıdaki dil öğelerinden oluşur:
Dil unsurları ' #define
' değerleri olarak tanımlanmıştır :
#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
ve her bir elemanı depolamak için aşağıdaki yapı kullanılmıştır:
typedef struct {
int typ;
union {
char *str;
int val;
}
} tElem;
her bir öğenin boyutu, maksimum birleşimin boyutuydu (tip için 4 bayt ve birleşim için 4 bayt, bunlar tipik değerler olsa da, gerçek boyutlar uygulamaya bağlıdır).
Bir "set" öğesi oluşturmak için şunları kullanırsınız:
tElem e;
e.typ = ELEM_SYM_SET;
Bir "değişken [b]" öğesi oluşturmak için şunları kullanırsınız:
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
Bir "sabit [7]" öğesi oluşturmak için şunları kullanırsınız:
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
ve kolayca şamandıralar ( float flt
) veya gerekçeler ( struct ratnl {int num; int denom;}
) ve diğer türleri içerecek şekilde genişletebilirsiniz .
Temel dayanak noktası, bellekte bitişik olmaması str
ve val
aslında üst üste gelmemesidir, bu nedenle, burada gösterilen, yapının bellek konumuna dayalı olduğu 0x1010
ve tamsayıların ve işaretçilerin her ikisinin de bulunduğu aynı bellek bloğunda farklı bir görünüm elde etmenin bir yoludur. 4 bayt:
+-----------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-----+-----+
0x1014 | | |
0x1015 | str | val |
0x1016 | | |
0x1017 | | |
+-----+-----+
Sadece bir yapıda olsaydı şöyle olurdu:
+-------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-------+
0x1014 | |
0x1015 | str |
0x1016 | |
0x1017 | |
+-------+
0x1018 | |
0x1019 | val |
0x101A | |
0x101B | |
+-------+
make sure you free this later
açıklama sabiti elemanı çıkarılabilir?
Farklı şekillerde kullanılabilen hafızanın yeniden kullanılmasını kolaylaştırdığını söyleyebilirim, yani hafıza tasarrufu. Örneğin, kısa bir dizeyi ve bir sayıyı kaydedebilen bir "varyant" yapısı yapmak istersiniz:
struct variant {
int type;
double number;
char *string;
};
32 bitlik bir sistemde bu, her bir örnek için en az 96 bit veya 12 bayt kullanılmasına neden olur. variant
.
Bir birleşim kullanarak boyutu 64 bit veya 8 bayta kadar azaltabilirsiniz:
struct variant {
int type;
union {
double number;
char *string;
} value;
};
Daha farklı değişken türleri vb. Eklemek isterseniz daha da fazla tasarruf sağlayabilirsiniz. Doğru bir işaretçi kullanarak benzer şeyler yapabileceğiniz doğru olabilir - ancak birlik, onu hem tür hem de daha erişilebilir hale getirir. kasa. Bu tür tasarruflar kulağa büyük gelmiyor, ancak bu yapının tüm örnekleri için kullanılan belleğin üçte birini tasarruf ediyorsunuz.
Bu tür esnek bir yapıya ihtiyacınız olduğunda, belki de farklı boyutlarda mesajlar göndereceğiniz bir mesaj protokolünde belirli bir durumu düşünmek zordur, ancak o zaman bile muhtemelen daha iyi ve daha programcı dostu alternatifler vardır.
Sendikalar diğer dillerdeki değişken türlerine benzer - bir seferde sadece bir şey tutabilirler, ancak bunu nasıl beyan ettiğinize bağlı olarak bir int, bir float vb.
Örneğin:
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnion, en son ayarladığınıza bağlı olarak yalnızca bir int VEYA float içerir . Yani bunu yapıyor:
MYUNION u;
u.MyInt = 10;
u şimdi 10'a eşit bir int'e sahiptir;
u.MyFloat = 1.0;
u şimdi 1.0'a eşit bir şamandıra tutar. Artık bir int içermiyor. Açıkçası şimdi printf ("MyInt =% d", u.MyInt); belirli davranıştan emin olmadığım halde muhtemelen bir hata alırsınız.
Birliğin büyüklüğü, en büyük alanının, bu durumda şamandıra büyüklüğüne göre belirlenir.
sizeof(int) == sizeof(float)
( == 32
) genellikle.
Sendikalar, donanım, aygıtlar veya ağ protokolleri tarafından tanımlanan yapıları modellemek istediğinizde veya çok sayıda nesne oluşturduğunuzda ve yerden tasarruf etmek istediğinizde kullanılır. Gerçekten de onlara% 95 ihtiyaç duymazsınız, hata ayıklaması kolay kodla sadık kalın.
Bu cevapların çoğu, bir türden diğerine döküm ile ilgilidir. Ben sadece daha fazla aynı tür sendikalar en iyi kullanımı (yani bir seri veri akışı ayrıştırırken). Çerçeveli bir paketin ayrıştırılmasını / oluşturulmasını önemsiz hale getirir .
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..
}
}
Düzenle Endianness ve struct padding hakkındaki yorum geçerli ve büyük endişelerdir. Bu kod gövdesini neredeyse tamamen gömülü yazılımda kullandım, çoğu borunun her iki ucunu da kontrol ettim.
Sendikalar harika. Gördüğüm sendikaların akıllıca bir kullanımı, bir olay tanımlarken onları kullanmaktır. Örneğin, bir etkinliğin 32 bit olduğuna karar verebilirsiniz.
Şimdi, bu 32 bit içinde, olayı gönderenin tanımlayıcısı olarak ilk 8 biti atamak isteyebilirsiniz ... Bazen olayla bir bütün olarak ilgilenirsiniz, bazen diseksiyon yapar ve bileşenlerini karşılaştırırsınız. sendikalar her ikisini de yapma esnekliği sağlar.
birlik Etkinliği { imzasız uzun eventCode; unsigned char eventParts [4]; };
Peki ya VARIANT
COM arayüzlerinde kullanılır? İki türü vardır - "tür" ve "tür" alanına bağlı olarak işlenen gerçek değeri tutan bir birlik.
Gömülü cihazlar için kod yazarken sendika kullandım. 16 bit uzunluğunda C int var. Ve EEPROM'dan okuma / saklamaya ihtiyaç duyduğumda daha yüksek 8 biti ve daha düşük 8 biti almam gerekiyor. Bu şekilde kullandım:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
Vites değiştirme gerektirmez, böylece kodun okunması daha kolaydır.
Öte yandan, stl ayırıcı için birlik kullanılan bazı eski C ++ stl kodu gördüm. Eğer ilgileniyorsanız, sgi stl kaynak kodunu okuyabilirsiniz . İşte bir parçası:
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
struct
Cihazınızın etrafında bir gruplamaya ihtiyacınız olmaz mı ? Şu anda her ikisi de sadece ilk baytı göstermelidir. higher
lower
Şuna bir göz atın: X.25 arabellek komutu işleme
Olası birçok X.25 komutundan biri, bir tampona alınır ve olası tüm yapıların BİRLİĞİ kullanılarak yerinde işlenir.
C'nin ilk sürümlerinde, tüm yapı bildirimleri ortak bir alan kümesini paylaşırdı. Verilen:
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];};
bir derleyici temelde yapıların boyutlarının (ve muhtemelen hizalamalarının) bir tablosunu ve yapıların üyelerinin adlarının, türlerinin ve ofsetlerinin ayrı bir tablosunu üretir. Derleyici üyeleri hangi yapılara ait olan izlemek vermedi ve iki yapının üyesi ile olduğu gibi (tip ve eşleşen ofset yalnızca aynı isimde bir üye olmasını sağlayacak q
bir struct x
vestruct y
). P, herhangi bir yapı türüne işaretçi olsaydı, p-> q, işaretçi p'ye "q" ofsetini ekler ve elde edilen adresten bir "int" getirir.
Yukarıdaki semantikler göz önüne alındığında, söz konusu yapılar içindeki yararlı alanlarla dizilmiş olması şartıyla, işlev tarafından kullanılan tüm alanların birbirinin yerine kullanılabilmesi için, birden çok yapı türü üzerinde bazı yararlı işlemler yapabilen bir fonksiyon yazmak mümkün olmuştur. Bu yararlı bir özellikti ve söz konusu yapı türlerine karşı yapı erişimi için kullanılan üyeleri doğrulamak üzere C'yi değiştirmek, aynı adreste birden çok adlandırılmış alan içerebilen bir yapıya sahip olmanın bir yolu olmadığında onu kaybetmek anlamına gelirdi. C'ye "birleşim" türlerinin eklenmesi bu boşluğun bir miktar doldurulmasına yardımcı oldu (olmasa da, IMHO ve olması gerektiği gibi).
Sendikaların bu boşluğu doldurma yeteneğinin önemli bir parçası, bir sendika üyesine yönelik bir işaretçinin, o üyeyi içeren herhangi bir sendikaya bir işaretçiye dönüştürülebilmesi ve herhangi bir sendikaya yönelik bir işaretçinin herhangi bir üyeye bir işaretçiye dönüştürülebilmesiydi. C89 Standart açıkça bir döküm söylemek etmezken T*
doğrudan için U*
her ikisini de içeren bir birlik türü için bir işaretçi olarak döküm eşdeğerdir T
ve U
bu döküm sonra ve U*
, ikinci döküm dizisinin tanımlanmış bir davranış ile etkilenecek birleşim türü kullanıldı ve Standart, ' T
den doğrudan yayın için herhangi bir ters anlambilim belirtmedi U
. Bundan başka, bir işlev bilinmeyen bir işaretçi ile bir nesne yazma davranışını alınan durumlardaT*
,T*
a U*
ve ardından nesneyi okumak, U*
türden bir üye aracılığıyla bir birleşim yazmaya ve tür T
olarak okumaya eşdeğerdir U
; bu, birkaç durumda standart olarak tanımlanır (örneğin Ortak Başlangıç Dizisi üyelerine erişirken) ve Uygulama Tanımlı (daha doğrusu) (tanımlanmamıştan daha). Programların CIS güvencelerini sendika türündeki gerçek nesnelerle sömürmesi nadir olmakla birlikte, bilinmeyen kökenlere yönelik göstergelerin sendika üyelerine işaretçiler gibi davranması ve bunlarla ilişkili davranışsal garantilere sahip olması gerçeğinden yararlanmak çok daha yaygındı.
foo
bir olan int
8 ofset, daha sonra anyPointer->foo = 1234;
geliyordu "8 bayt tarafından, yerdeğişime bunu anyPointer adresi almak ve elde edilen adrese değerin 1234 tamsayı deposu gerçekleştirin. Olmadığına derleyici bilmek ya da bakım gerek olmaz anyPointer
tespit foo
üyeleri arasında listelenen herhangi bir yapı türü
anyPointer
bir yapı üyesi ile kimlik doğrulaması yapıp yapmadığını bilmiyorsa , derleyici to have a member with the same name only if the type and offset matched
yayınınızın bu durumunu nasıl denetler ?
p->foo
, türüne ve ofsetine bağlı olacaktır foo
. Aslında, p->foo
kısaydı *(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
. İkinci sorunuza gelince, bir derleyici bir yapı üyesi tanımıyla karşılaştığında, bu ada sahip bir üye bulunmamasını veya bu ada sahip üyenin aynı tür ve ofsete sahip olmasını gerektirir; Eşleşmeyen bir yapı üyesi tanımı mevcut olsaydı, squawked olurdu tahmin ediyorum, ama nasıl hataları ele bilmiyorum.
Basit ve çok kullanışlı bir örnek ....
Hayal etmek:
Eğer bir var uint32_t array[2]
ve Byte zincirinin 3. ve 4. Byte erişmek istediğiniz. yapabilirsin *((uint16_t*) &array[1])
. Ama bu ne yazık ki katı takma kurallara aykırı!
Ancak bilinen derleyiciler aşağıdakileri yapmanızı sağlar:
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
teknik olarak bu hala kuralların ihlalidir. ancak bilinen tüm standartlar bu kullanımı desteklemektedir.