Ответы:
Объединения часто используются для преобразования между двоичными представлениями целых чисел и чисел с плавающей точкой:
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*
T
U
, что в некоторых случаях будет определено стандартом (например, при доступе к элементам 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];
}
технически это все еще является нарушением правил. но все известные стандарты поддерживают это использование.