Зачем нам нужны Союзы C? - PullRequest
217 голосов
/ 31 октября 2008

Когда следует использовать профсоюзы? Зачем они нам нужны?

Ответы [ 18 ]

237 голосов
/ 31 октября 2008

Союзы часто используются для преобразования двоичных представлений целых чисел и чисел с плавающей точкой:

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.

123 голосов
/ 31 октября 2008

Объединения особенно полезны во встроенном программировании или в ситуациях, когда необходим прямой доступ к оборудованию / памяти. Вот тривиальный пример:

typedef union
{
    struct {
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
        unsigned char byte4;
    } bytes;
    unsigned int dword;
} HW_Register;
HW_Register reg;

Затем вы можете получить доступ к регистру следующим образом:

reg.dword = 0x12345678;
reg.bytes.byte3 = 4;

Порядковый номер (порядок байтов) и архитектура процессора, конечно, важны.

Другая полезная функция - модификатор бита:

typedef union
{
    struct {
        unsigned char b1:1;
        unsigned char b2:1;
        unsigned char b3:1;
        unsigned char b4:1;
        unsigned char reserved:4;
    } bits;
    unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;

С помощью этого кода вы можете получить прямой доступ к одному биту в адресе регистра / памяти:

x = reg.bits.b2;
59 голосов
/ 29 августа 2011

Системное программирование низкого уровня является разумным примером.

IIRC, я использовал объединения для разбиения аппаратных регистров на биты компонентов. Таким образом, вы можете получить доступ к 8-битному регистру (как это было в тот день, когда я это сделал ;-) в биты компонента.

(я забыл точный синтаксис, но ...) Эта структура позволила бы получить доступ к регистру управления в виде control_byte или через отдельные биты. Было бы важно обеспечить отображение битов на правильные регистровые биты для заданного порядка байтов.

typedef union {
    unsigned char control_byte;
    struct {
        unsigned int nibble  : 4;
        unsigned int nmi     : 1;
        unsigned int enabled : 1;
        unsigned int fired   : 1;
        unsigned int control : 1;
    };
} ControlRegister;
33 голосов
/ 29 августа 2011

Я видел это в нескольких библиотеках как замену объектно-ориентированного наследования.

* 1003 Е.Г. *

        Connection
     /       |       \
  Network   USB     VirtualConnection

Если вы хотите, чтобы «класс» соединения был одним из перечисленных выше, вы можете написать что-то вроде:

struct Connection
{
    int type;
    union
    {
        struct Network network;
        struct USB usb;
        struct Virtual virtual;
    }
};

Пример использования в libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74

29 голосов
/ 31 октября 2008

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

В следующем примере:

union {
   int a;
   int b;
   int c;
} myUnion;

Это объединение будет занимать пространство одного int, а не 3 отдельных значений int. Если пользователь установит значение a , а затем установит значение b , он перезапишет значение a , поскольку они оба совместно используют одну и ту же память место.

25 голосов
/ 29 августа 2011

много употреблений. Просто сделайте grep union /usr/include/* или в похожих каталогах. В большинстве случаев union заключен в struct, и один член структуры сообщает, к какому элементу объединения необходимо получить доступ. Например оформить заказ man elf для реальных реализаций.

Это основной принцип:

struct _mydata {
    int which_one;
    union _data {
            int a;
            float b;
            char c;
    } foo;
} bar;

switch (bar.which_one)
{
   case INTEGER  :  /* access bar.foo.a;*/ break;
   case FLOATING :  /* access bar.foo.b;*/ break;
   case CHARACTER:  /* access bar.foo.c;*/ break;
}
17 голосов
/ 31 октября 2008

Вот пример объединения из моей собственной кодовой базы (из памяти и перефразировано, поэтому оно может быть неточным). Он использовался для хранения языковых элементов в интерпретаторе, который я построил. Например, следующий код:

set a to b times 7.

состоит из следующих языковых элементов:

  • символ [комплект]
  • переменная [а]
  • символ [к]
  • переменная [Ь]
  • символ [раз]
  • постоянная [7]
  • символ [.]

Элементы языка были определены как значения #define:

#define ELEM_SYM_SET        0
#define ELEM_SYM_TO         1
#define ELEM_SYM_TIMES      2
#define ELEM_SYM_FULLSTOP   3
#define ELEM_VARIABLE     100
#define ELEM_CONSTANT     101

и следующая структура использовалась для хранения каждого элемента:

typedef struct {
    int typ;
    union {
        char *str;
        int   val;
    }
} tElem;

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

Чтобы создать элемент "set", вы должны использовать:

tElem e;
e.typ = ELEM_SYM_SET;

Чтобы создать элемент «variable [b]», вы должны использовать:

tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later

Чтобы создать элемент «constant [7]», вы должны использовать:

tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;

, и вы можете легко расширить его, включив в него числа с плавающей точкой (float flt) или рациональные числа (struct ratnl {int num; int denom;}) и другие типы.

Основная предпосылка состоит в том, что str и val не являются смежными в памяти, они фактически перекрываются, поэтому это способ получить другое представление для того же блока памяти, проиллюстрированного здесь, где основывается структура в ячейке памяти 0x1010, а целые числа и указатели по 4 байта:

       +-----------+
0x1010 |           |
0x1011 |    typ    |
0x1012 |           |
0x1013 |           |
       +-----+-----+
0x1014 |     |     |
0x1015 | str | val |
0x1016 |     |     |
0x1017 |     |     |
       +-----+-----+

Если бы это было просто в структуре, это выглядело бы так:

       +-------+
0x1010 |       |
0x1011 |  typ  |
0x1012 |       |
0x1013 |       |
       +-------+
0x1014 |       |
0x1015 |  str  |
0x1016 |       |
0x1017 |       |
       +-------+
0x1018 |       |
0x1019 |  val  |
0x101A |       |
0x101B |       |
       +-------+
7 голосов
/ 29 августа 2011

Я бы сказал, что это упрощает повторное использование памяти, которая может использоваться по-разному, то есть экономия памяти. Например. Вы хотели бы создать некоторую "вариативную" структуру, которая может сохранить как короткую строку, так и число:

struct variant {
    int type;
    double number;
    char *string;
};

В 32-битной системе это приведет к тому, что для каждого экземпляра variant.

будет использовано не менее 96 бит или 12 байтов.

Используя объединение, вы можете уменьшить размер до 64 бит или 8 байт:

struct variant {
    int type;
    union {
        double number;
        char *string;
    } value;
};

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

5 голосов
/ 31 октября 2008

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

Союзы немного похожи на типы вариантов в других языках - они могут содержать только одну вещь за раз, но это может быть int, float и т. Д. В зависимости от того, как вы объявляете это.

Например:

typedef union MyUnion MYUNION;
union MyUnion
{
   int MyInt;
   float MyFloat;
};

MyUnion будет содержать только int ИЛИ float, , в зависимости от того, что вы недавно установили . Так делаем это:

MYUNION u;
u.MyInt = 10;

u теперь содержит int равное 10;

u.MyFloat = 1.0;

у вас теперь есть число с плавающей запятой, равное 1,0. Он больше не содержит int. Очевидно, теперь, если вы попытаетесь сделать printf ("MyInt =% d", u.MyInt); тогда вы, вероятно, получите ошибку, хотя я не уверен в конкретном поведении.

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

4 голосов
/ 31 октября 2008

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

...