Союз - бесполезный анахронизм или полезная уловка старой школы? - PullRequest
20 голосов
/ 13 мая 2009

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

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

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

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

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

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

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

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

Ответы [ 11 ]

24 голосов
/ 13 мая 2009

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

18 голосов
/ 13 мая 2009

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

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) { ...
10 голосов
/ 13 мая 2009

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

8 голосов
/ 13 мая 2009

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

6 голосов
/ 13 мая 2009

Объединение - это самый простой способ реализовать VARIANT-подобные типы данных в C / C ++, я полагаю.

5 голосов
/ 13 мая 2009

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

Например:

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 используется для определения того, какая часть объединения используется. Использование объединения позволило нам избежать дублирования общих частей структуры пакета.

4 голосов
/ 13 мая 2009

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

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

4 голосов
/ 13 мая 2009

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

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

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

2 голосов
/ 02 сентября 2009

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

#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 :: variable) и выполнение второго чтения на только что созданном пространстве.

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

2 голосов
/ 13 мая 2009

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

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

Представьте, что данные "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
...