Когда следует использовать профсоюзы? Зачем они нам нужны?
236
Объединения часто используются для преобразования между двоичными представлениями целых чисел и чисел с плавающей точкой:
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.
Объединения особенно полезны во встроенном программировании или в ситуациях, когда необходим прямой доступ к оборудованию / памяти. Вот тривиальный пример:
Затем вы можете получить доступ к регистру следующим образом:
Endianness (порядок байтов) и архитектура процессора, конечно, важны.
Еще одна полезная функция - модификатор битов:
С помощью этого кода вы можете получить прямой доступ к одному биту в адресе регистра / памяти:
источник
Низкоуровневое системное программирование является разумным примером.
IIRC, я использовал объединения, чтобы разбить аппаратные регистры на биты компонентов. Таким образом, вы можете получить доступ к 8-битному регистру (как это было в тот день, когда я это сделал ;-) в биты компонента.
(Я забыл точный синтаксис, но ...) Эта структура позволила бы получить доступ к регистру управления как control_byte или через отдельные биты. Было бы важно обеспечить отображение битов на правильные регистровые биты для заданного порядка байтов.
источник
Я видел это в нескольких библиотеках как замену объектно-ориентированного наследования.
Например
Если вы хотите, чтобы класс «Connection» был одним из перечисленных выше, вы можете написать что-то вроде:
Пример использования в libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74
источник
Объединения позволяют членам данных, которые являются взаимоисключающими, использовать одну и ту же память. Это очень важно, когда памяти меньше, например во встроенных системах.
В следующем примере:
Этот союз будет занимать пространство одного int, а не 3 отдельных значений int. Если пользователь установит значение a , а затем установит значение b , он перезапишет значение a, поскольку они оба совместно используют одну и ту же область памяти.
источник
Много обычаев. Просто сделайте
grep union /usr/include/*
или в похожих каталогах. В большинстве случаев,union
обернутый вstruct
и один член структуры сообщает, какой элемент в объединении получить доступ. Например оформить заказman elf
на реальные реализации.Это основной принцип:
источник
Вот пример объединения из моей собственной кодовой базы (из памяти и перефразировано, поэтому оно может быть неточным). Он использовался для хранения языковых элементов в интерпретаторе, который я построил. Например, следующий код:
состоит из следующих языковых элементов:
Элементы языка были определены как '
#define
' значения таким образом:и следующая структура была использована для хранения каждого элемента:
тогда размер каждого элемента был размером максимального объединения (4 байта для типа и 4 байта для объединения, хотя это типичные значения, фактические размеры зависят от реализации).
Чтобы создать элемент set, вы должны использовать:
Для создания элемента variable [b] вы должны использовать:
Чтобы создать элемент «constant [7]», вы должны использовать:
и вы можете легко расширить его, чтобы включить floats (
float flt
) или rationals (struct ratnl {int num; int denom;}
) и другие типы.Основная предпосылка заключается в том, что
str
иval
не являются смежными в памяти, они фактически перекрываются, поэтому это способ получить другое представление для одного и того же блока памяти, как показано здесь, где структура основана на ячейке памяти,0x1010
а целые и указатели являются 4 байта:Если бы это было просто в структуре, это выглядело бы так:
источник
make sure you free this later
ли удалить комментарий из константного элемента?Я бы сказал, что это облегчает повторное использование памяти, которая может использоваться по-разному, то есть экономия памяти. Например, вы хотели бы создать некоторую «вариативную» структуру, способную сохранить как короткую строку, так и число:
В 32-битной системе это приведет к тому, что по меньшей мере 96 бит или 12 байтов будут использоваться для каждого экземпляра
variant
.Используя объединение, вы можете уменьшить размер до 64 бит или 8 байт:
Вы можете сэкономить еще больше, если хотите добавить больше различных типов переменных и т. Д. Возможно, это правда, что вы можете делать аналогичные вещи с использованием пустого указателя - но объединение делает его намного более доступным, чем и тип. сейф. Такая экономия не кажется огромной, но вы экономите одну треть памяти, используемой для всех экземпляров этой структуры.
источник
Трудно придумать конкретный случай, когда вам понадобится такая гибкая структура, возможно, в протоколе сообщений, где вы будете отправлять сообщения разных размеров, но даже тогда, возможно, есть более лучшие и более дружественные для программиста альтернативы.
Объединения немного похожи на типы вариантов в других языках - они могут содержать только одну вещь за раз, но это может быть int, float и т. Д., В зависимости от того, как вы объявляете это.
Например:
MyUnion будет содержать только int или float, в зависимости от того, что вы установили в последний раз . Так делаем это:
теперь у вас есть целое число, равное 10;
Теперь у вас есть число с плавающей запятой, равное 1,0. Он больше не содержит int. Очевидно, теперь, если вы попытаетесь сделать printf ("MyInt =% d", u.MyInt); тогда вы, вероятно, получите ошибку, хотя я не уверен в конкретном поведении.
Размер объединения определяется размером его наибольшего поля, в данном случае поплавка.
источник
sizeof(int) == sizeof(float)
(== 32
) обычно.Объединения используются, когда вы хотите смоделировать структуры, определенные аппаратными средствами, устройствами или сетевыми протоколами, или когда вы создаете большое количество объектов и хотите сэкономить место. Они действительно вам не нужны в 95% случаев, придерживайтесь простого в отладке кода.
источник
Многие из этих ответов касаются преобразования из одного типа в другой. Я получаю наибольшую пользу от союзов с одними и теми же типами, только больше (например, при разборе последовательного потока данных). Они позволяют разбору / построению созданного пакета стать тривиальным.
Редактировать Комментарий о порядке байтов и структурных отступов является действительным и очень важным. Я почти полностью использовал эту часть кода во встроенном программном обеспечении, большую часть которого контролировал оба конца канала.
источник
Союзы великолепны. Одно умное использование союзов, которые я видел, состоит в том, чтобы использовать их при определении события. Например, вы можете решить, что событие 32-битное.
Теперь, в этих 32 битах, вы можете указать первые 8 бит в качестве идентификатора отправителя события ... Иногда вы имеете дело с событием в целом, иногда вы анализируете его и сравниваете его компоненты. профсоюзы дают вам гибкость, чтобы сделать оба.
источник
Как насчет того,
VARIANT
что используется в интерфейсах COM? Он имеет два поля - «тип» и объединение, содержащее фактическое значение, которое обрабатывается в зависимости от поля «тип».источник
В школе я использовал такие союзы:
Я использовал его для более удобной обработки цветов, вместо использования операторов >> и <<, мне просто нужно было просмотреть другой индекс моего массива char.
источник
Я использовал union, когда писал код для встроенных устройств. У меня есть C int, это 16 бит. И мне нужно извлечь старшие 8 бит и младшие 8 бит, когда мне нужно читать из / store в EEPROM. Поэтому я использовал этот способ:
Это не требует сдвига, поэтому код легче читать.
С другой стороны, я видел какой-то старый код C ++ stl, который использовал union для stl allocator. Если вы заинтересованы, вы можете прочитать SGI STL исходный код. Вот часть этого:
источник
struct
вокруг вашегоhigher
/lower
? Прямо сейчас оба должны указывать только на первый байт.Взгляните на это: обработка команды буфера X.25
Одна из многих возможных команд X.25 принимается в буфер и обрабатывается на месте с использованием UNION всех возможных структур.
источник
В ранних версиях C все объявления структуры имели общий набор полей. Дано:
компилятор, по сути, будет создавать таблицу размеров структур (и, возможно, выравнивания), а также отдельную таблицу имен, типов и смещений элементов структур. Компилятор не уследить из которых члены принадлежали к какой структуре, и позволил бы две структуры , чтобы иметь элемент с таким же именем , только если типа и смещение согласованные (как с членом
q
вstruct x
иstruct y
). Если бы p был указателем на какой-либо тип структуры, p-> q добавило бы смещение «q» к указателю p и извлекло бы «int» из полученного адреса.Учитывая вышеупомянутую семантику, можно было написать функцию, которая могла бы выполнять некоторые полезные операции над несколькими типами структур взаимозаменяемо, при условии, что все поля, используемые функцией, выровнены с полезными полями в рассматриваемых структурах. Это была полезная функция, и изменение C для проверки элементов, используемых для доступа к структуре, по отношению к типам рассматриваемых структур означало бы потерю его в отсутствие средства, имеющего структуру, которая может содержать несколько именованных полей по одному и тому же адресу. Добавление типов «объединение» в C помогло несколько заполнить этот пробел (хотя, не так, IMHO, как и следовало бы).
Существенной частью способности объединений восполнить этот пробел является тот факт, что указатель на член объединения может быть преобразован в указатель на любое объединение, содержащее этот член, а указатель на любой объединение может быть преобразован в указатель на любой член. В то время как Стандарт C89 прямо не сказал, что приведение a , а затем чтение объекта через эквивалентно написанию объединения через член типа и чтению как тип
T*
непосредственно к aU*
было эквивалентно приведению его к указателю на любой тип объединения, содержащему оба,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])
. Но это, к сожалению, нарушает строгие правила наложения имен!Но известные компиляторы позволяют вам делать следующее:
технически это все еще является нарушением правил. но все известные стандарты поддерживают это использование.
источник