Зачем нужно объединение (union)?

488
06 февраля 2017, 16:39

Например,

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 потеряли :(

Опять-таки какой в этом смысл, если я могу сначала завести отдельную переменную int a = 20; а затем изменю ее значение a = 50; и эффект точно такой же? Выглядит как сильное колдовство.

Answer 1

Union-ы (объединения) используют в двух случаях:

  1. Для создания «универсального» типа данных, способного хранить не единственный, а один из предопределённых типов. Для этого к объединению добавляют целочисленное поле, указывающее тип хранимых в настоящий момент данных:

    struct variant
    {
        union tag_value
        {
            int intValue;
            float floatValue
        } value;
        unsigned storedType;
    };
    

    Один из примеров подобного применения в реальной жизни — структура VARIANT из Windows API.

    Иными словами, это предшественник современных boost::variant, QVariant и т. д. Однако вышеперечисленные классы могут хранить в себе непримитивные типы (с конструкторами, деструкторами и операторами копирования), а union — нет.

  2. Для преобразования между несовместимыми типами. Традиционно для этих целей используют оператор преобразования (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 as the member last used to store a value in the object, the appropriate part of the object 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.

Answer 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.

Иными словами, в наше время они, в общем-то не нужны.

Answer 3

Если я правильно понял, то это совмещение переменных в памяти. Иногда оч удобно, если хочется переменную увидеть в другом свете или требуется получить доступ только к части переменной...

К примеру String можно интерпритировать как массив byte[] или прямоугольный массив byte[][2], a integer разложить на byte_lo и byte_hi

Answer 4

Один из практических способов использование 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, но и доступ к битам появляется сразу.

READ ALSO
Проблема подключения Lua к C++

Проблема подключения Lua к C++

Использую исходный код lua-53

428
Cimg, jpeglib и visual studio

Cimg, jpeglib и visual studio

Никак не могу разобраться как заставить работать Cimg с jpegПодключить всё подключил:

716
Не умирает процесс (WinAPI)

Не умирает процесс (WinAPI)

ПриветПосле закрытия окна процесс не хочет умирать

445