Ответы:
Объединения часто используются для преобразования между двоичными представлениями целых чисел и чисел с плавающей точкой:
union
{
int i;
float f;
} u;
// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);
Хотя это технически неопределенное поведение в соответствии со стандартом C (вы должны только читать поле, которое было написано совсем недавно), оно будет действовать четко определенным образом практически в любом компиляторе.
Объединения также иногда используются для реализации псевдополиморфизма в 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;
Endianness (порядок байтов) и архитектура процессора, конечно, важны.
Еще одна полезная функция - модификатор битов:
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
Если вы хотите, чтобы класс «Connection» был одним из перечисленных выше, вы можете написать что-то вроде:
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обернутый в 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;
Для создания элемента variable [b] вы должны использовать:
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b"); // make sure you free this later
Чтобы создать элемент «constant [7]», вы должны использовать:
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
и вы можете легко расширить его, чтобы включить floats ( float flt) или rationals ( 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;
теперь у вас есть целое число, равное 10;
u.MyFloat = 1.0;
Теперь у вас есть число с плавающей запятой, равное 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 бит в качестве идентификатора отправителя события ... Иногда вы имеете дело с событием в целом, иногда вы анализируете его и сравниваете его компоненты. профсоюзы дают вам гибкость, чтобы сделать оба.
Событие союза
{
unsigned long eventCode;
unsigned char eventParts [4];
};
Как насчет того, VARIANTчто используется в интерфейсах COM? Он имеет два поля - «тип» и объединение, содержащее фактическое значение, которое обрабатывается в зависимости от поля «тип».
Я использовал union, когда писал код для встроенных устройств. У меня есть C int, это 16 бит. И мне нужно извлечь старшие 8 бит и младшие 8 бит, когда мне нужно читать из / store в EEPROM. Поэтому я использовал этот способ:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
Это не требует сдвига, поэтому код легче читать.
С другой стороны, я видел какой-то старый код C ++ stl, который использовал union для stl allocator. Если вы заинтересованы, вы можете прочитать 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 помогло несколько заполнить этот пробел (хотя, не так, IMHO, как и следовало бы).
Существенной частью способности объединений восполнить этот пробел является тот факт, что указатель на член объединения может быть преобразован в указатель на любое объединение, содержащее этот член, а указатель на любой объединение может быть преобразован в указатель на любой член. В то время как Стандарт C89 прямо не сказал, что приведение a , а затем чтение объекта через эквивалентно написанию объединения через член типа и чтению как типT* непосредственно к a U*было эквивалентно приведению его к указателю на любой тип объединения, содержащему оба, Tи U, а затем приведение к этому U*, никакое определенное поведение последней последовательности преобразования не будет затронуто Используется тип объединения, и в стандарте не указывается какой-либо противоположной семантики для прямого приведения из Tв U. Кроме того, в случаях, когда функция получает указатель неизвестного происхождения, поведение записи объекта посредством T*преобразованияT*в aU*U*TU , что в некоторых случаях будет определено стандартом (например, при доступе к элементам Common Initial Sequence) и определено реализацией (а не неопределено) ) что касается прочего. В то время как программы редко использовали гарантии CIS с действительными объектами типа объединения, гораздо чаще использовался тот факт, что указатели на объекты неизвестного происхождения должны были вести себя как указатели на члены объединения и иметь связанные с этим поведенческие гарантии.
fooявляется intсо смещением 8, то anyPointer->foo = 1234;означает «взять адрес в anyPointer, сместите его на 8 байт, а также выполнять целое хранилище значения 1234 для результирующего адреса. Компилятор не должен был бы знать , или ухода ли anyPointerидентифицированы любой тип конструкции, который был fooуказан среди ее членов.
anyPointerидентифицирует ли это элемент struct, то как компилятор проверит эти условия 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]и вы хотите получить доступ к третьему и четвертому байту цепочки байтов. ты мог бы сделать*((uint16_t*) &array[1]) . Но это, к сожалению, нарушает строгие правила наложения имен!
Но известные компиляторы позволяют вам делать следующее:
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
технически это все еще является нарушением правил. но все известные стандарты поддерживают это использование.