Відповіді:
Союзи часто використовуються для перетворення між двійковими представленнями цілих чисел і плавців:
union
{
int i;
float f;
} u;
// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);
Хоча це технічно невизначена поведінка відповідно до стандарту C (ви повинні читати лише те поле, яке було нещодавно написано), воно буде діяти чітко визначеним чином практично у будь-якому компіляторі.
Союзи також іноді використовуються для реалізації псевдополіморфізму в С, надаючи структурі деякий тег із зазначенням того, який тип об'єкта він містить, а потім об'єднання можливих типів разом:
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;
}
}
Це дозволяє розміру struct S
лише 12 байт, а не 28.
Профспілки особливо корисні у вбудованому програмуванні або в ситуаціях, коли потрібен прямий доступ до апаратного забезпечення / пам'яті. Ось тривіальний приклад:
typedef union
{
struct {
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
} bytes;
unsigned int dword;
} HW_Register;
HW_Register reg;
Тоді ви можете отримати доступ до регістру наступним чином:
reg.dword = 0x12345678;
reg.bytes.byte3 = 4;
Звичайно важливі ендіанси (порядок байтів) та архітектура процесора.
Ще одна корисна особливість - бітовий модифікатор:
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;
За допомогою цього коду ви можете отримати доступ безпосередньо до одного біта в реєстрі / адресі пам'яті:
x = reg.bits.b2;
Системне програмування низького рівня є розумним прикладом.
IIRC, я використовував об'єднання для розбиття апаратних регістрів на біти компонентів. Отже, ви можете отримати доступ до 8-бітного реєстру (як це було в день, коли я це зробив ;-) в біти компонентів.
(Я забуваю точний синтаксис, але ...) Ця структура дозволила б отримати доступ до реєстру управління як control_byte або через окремі біти. Важливо було б забезпечити карту бітів на правильних бітах регістра для даної ендіанності.
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;
Я бачив це в декількох бібліотеках як заміну об'єктно-орієнтованого успадкування.
Напр
Connection
/ | \
Network USB VirtualConnection
Якщо ви хочете, щоб клас "З'єднання" був одним із перерахованих вище, ви можете написати щось на зразок:
struct Connection
{
int type;
union
{
struct Network network;
struct USB usb;
struct Virtual virtual;
}
};
Приклад використання в libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74
Профспілки дозволяють членам даних, які взаємовиключають один одного, спільну пам'ять. Це досить важливо, коли пам'ять є дефіцитнішою, наприклад у вбудованих системах.
У наступному прикладі:
union {
int a;
int b;
int c;
} myUnion;
Цей об'єднання займе простір єдиного int, а не 3 окремих значення int. Якщо користувач встановив значення a , а потім встановив значення b , це перезаписало б значення a, оскільки вони обидва ділять однакове місце пам'яті.
Багато звичок. Просто робіть grep union /usr/include/*
або в подібних довідниках. У більшості випадків union
вкладений в a, struct
а один член структури повідомляє, до якого елемента в союзі можна отримати доступ. Наприклад, замовлення man elf
реалізованих реалій.
Це основний принцип:
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;
}
Ось приклад об'єднання з моєї власної кодової бази (з пам'яті та перефразоване, тому це може бути не точно). Він використовувався для зберігання мовних елементів у вбудованому мені інтерпретаторі. Наприклад, наступний код:
set a to b times 7.
складається з таких мовних елементів:
Мовні елементи визначаються як " #define
" значення таким чином:
#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
і для зберігання кожного елемента була використана наступна структура:
typedef struct {
int typ;
union {
char *str;
int val;
}
} tElem;
то розмір кожного елемента був розміром максимального об'єднання (4 байти для типу і 4 байти для об'єднання, хоча це типові значення, фактичні розміри залежать від реалізації).
Для того щоб створити елемент "set", ви використовуєте:
tElem e;
e.typ = ELEM_SYM_SET;
Для створення елемента "змінний [b]" ви використовуєте:
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
Для створення елемента "константа [7]" ви використовуєте:
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
і ви можете легко розширити його, включаючи floats ( float flt
) або раціоналі ( struct ratnl {int num; int denom;}
) та інші типи.
Основна передумова полягає в тому, що str
і val
не є суміжними в пам'яті, вони фактично перекриваються, тому це спосіб отримати інший погляд на той самий блок пам'яті, проілюстрований тут, де структура заснована на розташуванні пам'яті, 0x1010
а цілі числа та покажчики - це обидва 4 байти:
+-----------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-----+-----+
0x1014 | | |
0x1015 | str | val |
0x1016 | | |
0x1017 | | |
+-----+-----+
Якби вона була просто в структурі, вона виглядала б так:
+-------+
0x1010 | |
0x1011 | typ |
0x1012 | |
0x1013 | |
+-------+
0x1014 | |
0x1015 | str |
0x1016 | |
0x1017 | |
+-------+
0x1018 | |
0x1019 | val |
0x101A | |
0x101B | |
+-------+
make sure you free this later
слід вилучити коментар із постійного елемента?
Я б сказав, що це полегшує повторне використання пам'яті, яка може бути використана різними способами, тобто збереження пам'яті. Наприклад, ви хочете зробити структуру "варіанту", яка може зберегти короткий рядок, а також число:
struct variant {
int type;
double number;
char *string;
};
У 32-бітовій системі це призведе до використання щонайменше 96 біт або 12 байтів для кожного примірника variant
.
За допомогою об'єднання ви можете зменшити розмір до 64 біт або 8 байтів:
struct variant {
int type;
union {
double number;
char *string;
} value;
};
Ви можете зекономити ще більше, якщо хочете додати більше різних типів змінних і т.д. сейф. Такі заощадження не здаються великими, але ви економите третину пам'яті, яка використовується для всіх примірників цієї структури.
Важко придумати конкретний випадок, коли вам знадобиться такий тип гнучкої структури, можливо, в протоколі повідомлень, куди ви надсилатимете повідомлення різного розміру, але навіть тоді, мабуть, є кращі та більш зручні для програміста альтернативи.
Союзи трохи схожі на варіанти варіантів в інших мовах - вони можуть містити лише одну річ за часом, але ця річ може бути int, float і т. Д. Залежно від того, як ви це заявляєте.
Наприклад:
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnion буде містити лише int або float, залежно від того, який ви встановили останнім часом . Отже, роблячи це:
MYUNION u;
u.MyInt = 10;
u тепер має інт, рівний 10;
u.MyFloat = 1.0;
u тепер містить поплавок, рівний 1,0. Він більше не містить int. Очевидно тепер, якщо ви спробуєте виконати printf ("MyInt =% d", u.MyInt); то, ймовірно, ви отримаєте помилку, хоча я не впевнений у конкретній поведінці.
Розмір об'єднання продиктований розміром його найбільшого поля, в даному випадку поплавця.
sizeof(int) == sizeof(float)
( == 32
) зазвичай.
Союзи використовуються, коли ви хочете моделювати структури, визначені апаратними засобами, пристроями або мережевими протоколами, або коли ви створюєте велику кількість об'єктів і хочете заощадити місце. Вони вам справді не потрібні 95% часу, але дотримуйтесь простого налагодження коду.
Багато з цих відповідей стосуються передачі від одного типу до іншого. Я отримую найбільшу користь від спілок з тими ж типами, що більше їх (тобто, при аналізі послідовного потоку даних). Вони дозволяють розбору / побудови пакетної рамки стати дрібницею.
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..
}
}
Редагувати Коментар про витривалість та накладення структури є поважними та великими проблемами. Я використовував цей фрагмент коду майже повністю у вбудованому програмному забезпеченні, більшість з якого я мав контроль над обома кінцями труби.
Спілки чудові. Одне розумне використання спілок, які я бачив, - це використовувати їх під час визначення події. Наприклад, ви можете вирішити, що подія має 32 біти.
Тепер, у межах 32-х бітів, ви можете позначити перші 8 біт як ідентифікатор відправника події ... Іноді ви маєте справу з подією в цілому, іноді ви розчленовуєте її та порівнюєте її компоненти. профспілки дають вам можливість робити і те, і інше.
союзний захід { неподписаний довгий eventCode; Непідписані події CharParts [4]; };
Що про VARIANT
те, що використовується в COM-інтерфейсах? Він має два поля - "type" та об'єднання, що містить фактичне значення, яке обробляється залежно від поля "type".
Я використовував об'єднання, коли кодував вбудовані пристрої. У мене є C int, який довгий 16 біт. І мені потрібно отримати вищі 8 біт і нижчі 8 біт, коли мені потрібно читати з / зберігати в EEPROM. Тому я використав такий спосіб:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
Це не вимагає зміщення, тому код легше читати.
З іншого боку, я побачив старий код C ++ stl, який використовував союз для виділення stl. Якщо вас цікавить, ви можете прочитати вихідний код sgi stl . Ось його фрагмент:
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
struct
навколо вашого higher
/ lower
? Зараз обидва повинні вказувати лише на перший байт.
Погляньте на це: обробка командами буфера X.25
Одна з безлічі можливих команд X.25 приймається в буфер і обробляється на місці за допомогою UNION усіх можливих структур.
У ранніх версіях C всі декларації структури мали б спільний набір полів. Подано:
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];};
компілятор, по суті, створить таблицю розмірів структур (і можливо вирівнювання) та окрему таблицю імен, типів та зрушень членів структур. Компілятор встежити з яких члени належали до якої структури, і дозволив би дві структури , щоб мати елемент з таким же ім'ям , тільки якщо типу і зсув узгоджені (як з членом q
в struct x
і struct y
). Якщо p був вказівником на будь-який тип структури, p-> q додав би зміщення "q" до вказівника p та отримав "int" з отриманої адреси.
Враховуючи викладену вище семантику, можна було записати функцію, яка могла б виконувати деякі корисні операції над різними типами структури взаємозамінно, за умови, що всі поля, що використовуються функцією, вишикувалися корисними полями у відповідних структурах. Це було корисною ознакою, і зміна C на перевірку членів, що використовуються для доступу до структури, стосовно типів структур, про які йдеться, означало б втрату її за відсутності засобу, що має структуру, яка може містити кілька названих полів за однією адресою. Додавання типів "об'єднання" до C допомогло дещо заповнити цю прогалину (хоча і ні, ІМХО, як і повинно було бути).
Важливою частиною здатності профспілок заповнити цей проміжок був той факт, що вказівник на члена профспілки може бути перетворений в покажчик на будь-який союз, що містить цього члена, а вказівник на будь-який союз може бути перетворений в покажчик на будь-який член. У той час як стандарт C89 прямо не сказав, що передача T*
прямого на a U*
еквівалентна переливанню його на вказівник на будь-який тип об'єднання, що містить T
і U
, і потім підкидає це U*
, жодна визначена поведінка останньої послідовності передачі не впливатиме на тип з'єднання, що використовується, а Стандарт не вказав жодної протилежної семантики для прямої T
передачі від U
. Далі, у випадках, коли функція отримала вказівник невідомого походження, поведінка запису об'єкта через аT*
, перетворення наT*
U*
, а потім читання об'єкта через U*
було б еквівалентно написанню об'єднання через член типу T
та читання як тип U
, що було б стандартно визначено в кількох випадках (наприклад, при доступі до загальних членів початкової послідовності) та визначено реалізацією (а не невизначено) ) для решти. Хоча програми по використанню гарантій СНД фактичними об'єктами союзного типу були рідкісними, але значно частіше було використовувати той факт, що вказівники на об'єкти невідомого походження повинні поводитись як вказівники на членів профспілки та пов'язані з цим поведінкові гарантії.
foo
є int
зсув 8, то anyPointer->foo = 1234;
мається на увазі "взяти адресу в будь-якомуPointer, перемістити її на 8 байт і виконати цілочисельне зберігання значення 1234 за отриманою адресою. Компілятору не потрібно знати чи дбати про те, чи anyPointer
ідентифікований будь-який тип структури, який був foo
вказаний серед його членів.
anyPointer
вказується особа з членом структури, то як компілятор перевірятиме ці умови to have a member with the same name only if the type and offset matched
вашої посади?
p->foo
буде залежати від типу та зсуву foo
. По суті, це p->foo
був стенограф для *(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
. Що стосується останнього запитання, коли компілятор стикається з визначенням члена структури, він вимагає, щоб або не було члена з цим іменем, або щоб член з цим іменем мав однаковий тип і зміщений; Я б здогадався, що це було б зірвано, якби існувало невідповідне визначення визначення структури, але я не знаю, як воно обробляло помилки.
Простий і дуже корисний приклад - це….
Уявіть собі:
Ви маєте uint32_t array[2]
і хочете отримати доступ до 3-го та 4-го байтів ланцюжка "Байт". ви могли б зробити *((uint16_t*) &array[1])
. Але це сумно порушує суворі правила псевдоніму!
Але відомі компілятори дозволяють зробити наступне:
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
технічно це все-таки порушення правил. але всі відомі стандарти підтримують це використання.