Например,
union
{
float fl,
unsigned int uinteg,
char ch,
int integ
} foo;
Все это хранится вперемешку в одной области памяти. Какой в этом смысл, ведь однажды установив значения
foo.fl = 3.14f;
foo.uinteg = 666;
foo.ch = 'a';
foo.int = -25;
уже не получится получить их назад - все перемешается? Способ сэкономить пару байто
или пару тактов и при этом сохранить читабельность? Не писать 4 разных функции, а написат
одну, которая принимает union и в ней уже решать, что делать? В таком случае не проще ли принять void * и потом кастануть в тот тип, какой нужен? Как пример "Просто кастануть" приведу такой код:
Классический пример:
typedef enum { STR, INT } tType;
typedef struct {
tType typ; // typ is separate.
union {
int ival; // ival and sval occupy same memory.
char *sval;
};
} tVal;
void printer(tVal uni)
{
if (uni.type == INTEGER) // blah-blah
uni.ival // Используем integer
else
ini.sval // В противном случае
}
Функцию принтер можно переписать как-то так:
void printer(void* data, tType typ)
{
if (tType == INTEGER)
{
(int*)data // Чего-то делаем
}
}
Другой пример:
union
{
int a;
int b;
int c;
} bar;
bar.a = 20;
bar.b = 50; // Значение a потеряли :(
Опять-таки какой в этом смысл, если я могу сначала завести отдельную переменную in
a = 20; а затем изменю ее значение a = 50; и эффект точно такой же? Выглядит как сильное колдовство.
Ответы
Ответ 1
Union-ы (объединения) используют в двух случаях:
Для создания «универсального» типа данных, способного хранить не единственный,
один из предопределённых типов. Для этого к объединению добавляют целочисленное поле, указывающее тип хранимых в настоящий момент данных:
struct variant
{
union tag_value
{
int intValue;
float floatValue
} value;
unsigned storedType;
};
Один из примеров подобного применения в реальной жизни — структура VARIANT из Windows API.
Иными словами, это предшественник современных boost::variant, QVariant и т. д. Однак
вышеперечисленные классы могут хранить в себе непримитивные типы (с конструкторами, деструкторами и операторами копирования), а union — нет.
Для преобразования между несовместимыми типами. Традиционно для этих целей использую
оператор преобразования (T), либо reinterpret_cast<>. Однако эти способы опасны нарушением strict aliasing rule и, как результат, порождением неопределённого (то есть непредсказуемого) поведения.
Правильные способы преобразования — это либо memcpy (подобный вызов которого выбрасывается компилятором), либо использование union-а.
UPD: Внимание! Преобразование через union является допустимым только в Си, но н
в C++. В ответе на вопрос «Accessing inactive union member and undefined behavior?» приводятся отсылки на следующие пункты стандартов:
c11
6.5.2.3 Структуры и члены объединений
95) Если поле, используемое для чтения содержимого объекта-объединения, не являетс
полем, использованным ранее для записи значения в этот объект, требуемая часть внутреннег
представления объекта интерпретируется в соответствием с представлением затребованного типа согласно 6.2.6 (данный процесс известен также как type punning). Это представление может приводить к неопределённому поведению.
6.5.2.3 Structure and union members
95) If the member used to read the contents of a union object is not the same a
the member last used to store a value in the object, the appropriate part of the objec
representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘type punning’’). This might be a trap representation.
c++11 (нет явного разрешения type punning-а)
9.5 Объединения [class.union]
В объединении в каждый момент времени может быть активно только одно нестатическо
поле; вследствие этого в объединении в любой момент времени может находиться не более одного значения.
9.5 Unions [class.union]
In a union, at most one of the non-static data members can be active at any time
that is, the value of at most one of the non-static data members can be stored in a union at any time.
Ответ 2
Вот что мне в своё время на похожий вопрос ответил Бьёрн Страуструп:
Unions are used when you want to save space by reusing space for
information one type at a time. I don't use them much.
Иными словами, в наше время они, в общем-то не нужны.
Ответ 3
Один из практических способов использование union это доступ к битам передаваемой информации. Допустим передается информация по битно за один раз 32 бита.
struct _info_s
{
uint32_t info_a:2;
uint32_t info_b:10;
uint32_t info_c:8;
uint32_t info_d:5;
uint32_t info_e:5;
uint32_t info_f:3;
}__atributte__((packed));
typedef struct _info_s info_s;
union _total_u
{
uint32_t array;
info_s info;
};
При чтении/записи мы можем сразу оперировать uint32_t, но и доступ к битам появляется сразу.
Ответ 4
Если я правильно понял, то это совмещение переменных в памяти. Иногда оч удобно
если хочется переменную увидеть в другом свете или требуется получить доступ только к части переменной...
К примеру String можно интерпритировать как массив byte[] или прямоугольный массив byte[][2], a integer разложить на byte_lo и byte_hi
Ответ 5
union очень полезная штука при работе с микроконтроллерами когда необходимо обрабатывать данные поступающие в "сыром" виде (например при передаче по CAN-сети или ethernet).
Например, при передаче по информационной сети возможны два тип сообщений:
typedef struct{
char id;
unsigned int code1;
unsigned int code2;
}Msg1;
typedef struct{
char id;
char code_error;
}Msg2;
Cоздаем объединение
union{
char data[maxLen];
Msg1 msg1;
Msg2 msg2;
}DATA;
Создаем переменную
DATA data;
Запись получаемых данных ведем в переменную data.data, а интерпретацию данных в сообщениях при помощи значений data.msg1 или data.msg2.
Также можно обойтись и без union напрямую преобразовывая указать на "сырые" данны
в указатель одного из видов сообщений и работать с полями сообщений, но при включении оптимизации (например -O2 или -O3) такой код перестает работать. Необходимо везде при объявлении переменных добавлять ключевое слово volatile.
Комментариев нет:
Отправить комментарий