Подтвердить что ты не робот

Союз - бесполезный анахронизм или полезный старый школьный трюк?

Недавно я столкнулся с большой книгой структур данных: Структуры данных с использованием C" (c) 1991, в местной библиотечной книжке для продажи всего $2. Как следует из названия книги, книга охватывает структуры данных с использованием языка программирования C.

Я получил книгу, зная, что это будет устаревшим, но, вероятно, будет содержать множество расширенных тем C, которые я бы не встретил в другом месте.

Конечно, в течение 5 минут я нашел кое-что, что я не знал о C. Я столкнулся с разделом, говорящим о ключевом слове union, и я понял, что никогда не использовал его и никогда не видел никакого кода. Я был благодарен за то, что научился чему-то интересному и быстро купил книгу.

Для тех из вас, кто не осведомлен о том, что такое союз, в книге используется хорошая метафора, чтобы объяснить:

Чтобы полностью понять концепцию союза, необходимо изучить его реализация. Структура может быть рассматривается как "дорожная карта" в Память. Он определяет, как память для толкования. Союз обеспечивает несколько различных дорожных карт для той же области памяти, и это ответственность программиста определить, какая дорожная карта находится в использовать. На практике компилятор выделяет достаточное количество хранилища для содержат самый большой член союз. Это дорожная карта, однако, который определяет, как это хранилище интерпретироваться.

Я мог бы легко придумать надуманные ситуации или хаки, где я бы использовал Союз. (Но меня не интересуют надуманные ситуации или хаки...)

Использовали ли вы или видели реализацию, в которой использование Союза решило проблему ** более элегантно **, чем не использовать Союз?

Добавлен бонус, если вы включили быстрое объяснение, почему использование союза было лучше/проще, чем не использовать объединение.

4b9b3361

Ответ 1

UNIONs реализуют какой-то полиморфизм в мире, отличном от ООП. Обычно у вас есть часть, которая является общей и в зависимости от этой части, вы используете остальные UNION. Поэтому в таких случаях, когда у вас нет языка ООП, и вы хотите избежать чрезмерной арифметики указателей, в некоторых случаях объединения могут быть более элегантными.

Ответ 2

Это полезно для установки битов в, скажем, регистровых, вместо операций сдвига/маски:

typedef union {
    unsigned int as_int; // Assume this is 32-bits
    struct {
        unsigned int unused1 : 4;
        unsigned int foo : 4;
        unsigned int bar : 6;
        unsigned int unused2 : 2;
        unsigned int baz : 3;
        unsigned int unused3 : 1;
        unsigned int quux : 12;
    } field;
} some_reg;

Примечание. В какой форме происходит упаковка, зависит от машины.

some_reg reg;
reg.field.foo = 0xA;
reg.field.baz = 0x5;
write_some_register(some_address, reg.as_int);

Я мог бы взорвать какой-то синтаксис где-то там, мой C ржавый:)

EDIT:

Кстати, это работает и наоборот:

reg.as_int = read_some_register(some_address);
if(reg.field.bar == BAR_ERROR1) { ...

Ответ 3

Действительно, это отличный инструмент, когда вы пишете такие вещи, как драйверы устройств (struct, которые вы хотите отправить на устройство, которое может иметь несколько похожих, но разных форматов), и вам требуется точное расположение памяти...

Ответ 4

Вы должны знать, что в С++ они не являются таким отличным решением, поскольку в объединение могут быть помещены только типы POD (простые старые данные). Если у вашего класса есть конструктор, деструктор, содержит классы, у которых есть конструкторы и/или деструкторы (и около миллиона других gotchas), он не может быть членом союза.

Ответ 6

Он часто используется в спецификации протоколов передачи данных, где вы хотите избежать потери пространства в своих структурах данных. Это позволяет сохранить пространство памяти, используя одно и то же пространство для нескольких взаимоисключающих опций.

Например:

enum PacketType {Connect, Disconnect};
struct ConnectPacket {};
struct DisconnectPacket {};
struct Packet
{
    // ...
    // various common data
    // ...
    enum PacketType type;
    union
    {
        ConnectPacket connect;
        DisconnectPacket disconnect;
    } payload;
};

Структуры ConnectPacket и DisconnectPacket занимают одно и то же пространство, но это нормально, потому что пакет не может быть одновременно двух типов. Значение enum используется для определения того, какая часть союза используется. Использование объединения позволило избежать дублирования общих частей структуры пакета.

Ответ 7

Рассмотрим случай доступа к отдельным байтам в большой переменной:

UInt32 x;
x = 0x12345678;
int byte_3 = x & 0x000000FF;          // 0x78
int byte_2 = (x & 0x0000FF00) >> 8;   // 0x56
int byte_1 = (x & 0x00FF0000) >> 16;  // 0x34
int byte_0 = (x & 0xFF000000) >> 24;  // 0x12

Это может быть гораздо более элегантным с объединением:

typedef union
{
    UInt32 value;  // 32 bits
    Byte byte[4];  // 4 * 8 bits
}
UInt32_Bytes;

UInt32_Bytes x;
x.value = 0x12345678;
int byte_3 = x.byte[3];  // 0x78
int byte_2 = x.byte[2];  // 0x56
int byte_1 = x.byte[1];  // 0x34
int byte_0 = x.byte[0];  // 0x12

Использование объединения означает, что вам больше не нужно использовать битовые маски и операторы сдвига для доступа к отдельным байтам. Это также делает явный доступ к байту.

Ответ 8

Это довольно хороший способ получить битовые значения IEEE для float (предполагая, что в вашей системе плавают IEEE). Все, что связано с литьем float * to int *, приводит к срыву правил строгого сглаживания. Это не просто теоретически - высокие уровни оптимизации фактически нарушают ваш код.

Технически, профсоюз не занимается проблемой. На практике все известные компиляторы будут (а) позволять вам писать один член объединения и читать обратно другой, и (б) выполнять чтение после выполнения записи. GCC по крайней мере способен свертывать объединение в регистр, превращая все это в no-op (предполагая, что поплавки хранятся в регистрах для начала).

Ответ 9

Мы использовали союзы в большом количестве кода для синтаксического анализа сетевых пакетов.

Союз выделяет размер самого большого элемента. Вы должны создать объединение с элементом буфера с максимальным размером сообщения, тогда вы можете легко получить доступ к значениям в пакете.

Представьте, что данные "c123456" поступают в сеть, и вам нужно проанализировать и получить доступ к значениям:

  #include <iostream>
  using namespace std;

  struct msg
  {
     char header;
     union
     {
       char a[3];
       char b[2];
       char c[5];
       char d[6];
       char buf[10];
     } data;
  } msg;

  int main()
  {
    struct msg m;
    memcpy(&m, "c123456", sizeof("c123456"));

    cout << "m.header: " << m.header << endl;
    cout << "m.data.d: " << string(m.data.d,sizeof(m.data.d)) << endl;
    cout << "m.data.b: " << string(m.data.b,sizeof(m.data.b)) << endl;

    switch (m.header)
    {
     case 'a': cout << "a: " << string(m.data.a, sizeof(m.data.a)) << endl; break;
     case 'b': cout << "b: " << string(m.data.b, sizeof(m.data.b)) << endl; break;
     case 'c': cout << "c: " << string(m.data.c, sizeof(m.data.c)) << endl; break;
     default: break;
    }
  }

Результат будет выглядеть так:

m.header: c
m.data.d: 123456
m.data.b: 12
c: 12345

Ответ 10

Я знаю, что это повторялось, но я просто опубликую образец кода, чтобы увидеть, как профсоюзы добавляют к элегантности и эффективности при чтении сетевого трафика:

#pragma packed(1)
struct header_t {
   uint16_t msg_id;
   uint16_t size;
};
struct command_t {
   uint8_t cmd;
};
struct position_t {
   uint32_t x;
   uint32_t y;
   uint32_t z;
};
// ... Rest of the messages in an IDS
struct message {
   header_t header;
   union {
      command_t command;
      position_t position;
   } body;
};
#pragma packed(0)
message read( int socket ) {
   message data;
   unsigned int readed = read( socket, &data, sizeof(header_t) );
   // error checks... readed bytes smaller than header size and such
   readed = read( socket, &(data.body), data.header.size ); 
   // error checks...
}

В приведенном выше фрагменте вы можете выполнить чтение сообщения на месте, и вам не нужно заботиться о конкретном типе полученного объекта. Если вы не использовали объединение, вы останетесь с чтением заголовка, извлекая как размер, так и тип, создавая экземпляр объекта соответствующего типа (либо в иерархии, либо чтобы включить внутри варианта типа как boost:: any/boost:: variant) и выполнение второго чтения во вновь создаваемом пространстве.

Мы широко используем это решение для управления симуляторами (некоторые компании не ценят "новые" технологии, такие как DDS или HLA, и по-прежнему зависят от необработанных данных UDP/TCP для своих симуляторов). На сетевом уровне мы используем объединения, которые преобразуются во внутренние структуры данных (преобразование между сетями, масштабирование данных...), прежде чем загружать их в прикладные уровни. Как уже упоминалось ранее, вы всегда должны быть осторожны с заполнением.

Ответ 11

Я использовал его один раз для грубого типа полиморфизма данных аналогично answerh44. У меня было несколько различных типов данных, которые я хотел бы использовать. Я создал объединение всех этих типов и структуру, содержащую объединение и код, определяющий, какой тип должен использоваться.


union
{
    data_type_1;
    data_type_2;
    data_type_3;
} data_union;

typedef struct _TAG_DATA_WRAPPED_
{
    data_union data;
    int data_type; //better an enum
} WRAPPED_DATA;

WRAPPED_DATA loads_of_data[1024];


Чтобы ответить на вопрос о том, почему это выгодно:

Это позволяет вам легко распределять списки или массивы разных типов данных и программно управлять их типом. Большая проблема - это, конечно же, пространство для хранения, потому что, если типы имеют очень разные размеры хранилища, вы можете тратить много места.