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

Зачем нам C-союзы?

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

4b9b3361

Ответ 1

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

union
{
  int i;
  float f;
} u;

// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);

Хотя это технически undefined поведение в соответствии со стандартом 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.

Ответ 2

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

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;

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

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

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;

Ответ 3

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

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;

Ответ 4

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

например.

        Connection
     /       |       \
  Network   USB     VirtualConnection

Если вы хотите, чтобы Connection "class" был либо одним из указанных выше, вы могли бы написать что-то вроде:

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

Ответ 5

Союзы

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

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

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

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

Ответ 6

Множество обычаев. Просто выполните 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;
}

Ответ 7

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

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

Чтобы создать "постоянный элемент [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 |       |
       +-------+

Ответ 8

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

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

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

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

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

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

Ответ 9

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

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

Например:

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

MyUnion будет содержать только int OR float, в зависимости от того, который вы установили в последнее время. Таким образом:

MYUNION u;
u.MyInt = 10;

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

u.MyFloat = 1.0;

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

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

Ответ 10

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

Ответ 11

Многие из этих ответов касаются кастинга от одного типа к другому. Я получаю наибольшее использование от объединений с одними и теми же типами только из них (т.е. При анализе потока последовательных данных). Они позволяют разбор/построение пакетного пакета стать тривиальным.

typedef union
{
    UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for
                               // the entire set of fields (including the payload)

    struct
    {
        UINT8 size;
        UINT8 cmd;
        UINT8 payload[PAYLOAD_SIZE];
        UINT8 crc;
    } fields;

}PACKET_T;

// This should be called every time a new byte of data is ready 
// and point to the packet buffer:
// packet_builder(packet.buffer, new_data);

void packet_builder(UINT8* buffer, UINT8 data)
{
    static UINT8 received_bytes = 0;

    // All range checking etc removed for brevity

    buffer[received_bytes] = data;
    received_bytes++;

    // Using the struc only way adds lots of logic that relates "byte 0" to size
    // "byte 1" to cmd, etc...
}

void packet_handler(PACKET_T* packet)
{
    // Process the fields in a readable manner
    if(packet->fields.size > TOO_BIG)
    {
        // handle error...
    }

    if(packet->fields.cmd == CMD_X)
    {
        // do stuff..
    }
}

Edit Замечание о endianness и struct padding справедливо и замечательно. Я использовал этот кусок кода почти полностью во встроенном программном обеспечении, большинство из которых я контролировал оба конца трубы.

Ответ 12

Как насчет VARIANT, который используется в интерфейсах COM? Он имеет два поля - "тип" и объединение, содержащее фактическое значение, которое обрабатывается в зависимости от поля типа.

Ответ 13

Я использовал объединение, когда я кодировал встроенные устройства. У меня есть C int, длина которого 16 бит. И мне нужно получить более высокие 8 бит и младшие 8 бит, когда мне нужно читать/хранить в EEPROM. Поэтому я использовал этот способ:

union data {
    int data;
    struct {
        unsigned char higher;
        unsigned char lower;
    } parts;
};

Это не требует смещения, поэтому код легче читать.

С другой стороны, я увидел старый код С++ stl, который использовал union для stl allocator. Если вам интересно, вы можете прочитать исходный код sgi stl. Вот его часть:

union _Obj {
    union _Obj* _M_free_list_link;
    char _M_client_data[1];    /* The client sees this.        */
};

Ответ 14

  • Файл, содержащий разные типы записей.
  • Сетевой интерфейс, содержащий разные типы запросов.

Взгляните на это: Обработка буфера X.25

Одна из многих возможных команд X.25 принимается в буфер и обрабатывается на месте с помощью UNION всех возможных структур.

Ответ 15

В школе я использовал такие союзы:

typedef union
{
  unsigned char color[4];
  int       new_color;
}       u_color;

Я использовал его для обработки цветов более легко, вместо того, чтобы использовать → и < операторам, мне просто пришлось пройти через другой индекс моего массива char.

Ответ 16

В ранних версиях C все объявления структуры имели бы общий набор полей. Дано:

struct x {int x_mode; int q; float x_f};
struct y {int y_mode; int q; int y_l};
struct z {int z_mode; char name[20];};

компилятор будет по существу создавать таблицу размеров структур (и, возможно, выравниваний), а также отдельную таблицу имен, типов и смещений членов структуры. Компилятор не отслеживал, какие члены принадлежали к каким структурам, и позволил бы двум структурам иметь член с тем же именем, только если тип и смещение совпадали (как с элементом q от struct x и struct y). Если p был указателем на любой тип структуры, p- > q добавит смещение "q" к указателю p и выберет "int" из результирующего адреса.

Учитывая приведенную выше семантику, можно было написать функцию, которая могла бы выполнять некоторые полезные операции над несколькими видами структуры взаимозаменяемо, при условии, что все поля, используемые функцией, выстроились в поле с полезными полями внутри рассматриваемых структур. Это была полезная функция, и изменение C для проверки членов, используемых для доступа к структуре, против типов рассматриваемых структур означало бы потерять его в отсутствие средств для создания структуры, которая может содержать несколько именованных полей по одному и тому же адресу. Добавление "union" типов в C помогло заполнить этот пробел несколько (хотя и не IMHO, как и должно было быть).

Важной частью способности профсоюзов восполнить этот пробел был тот факт, что указатель на член профсоюза может быть преобразован в указатель на любой союз, содержащий этот член, а указатель на любой союз может быть преобразован в указатель на любой член. В то время как в стандарте C89 не было однозначно сказано, что приведение T* непосредственно к U* эквивалентно тому, чтобы отнести его к указателю на любой тип объединения, содержащий как T, так и U, а затем листинг этого значения на U*, никакое определенное поведение последней последовательности броска не будет зависеть от используемого типа объединения, а в стандарте не будет указана какая-либо противоположная семантика для прямого перевода из T в U. Кроме того, в случаях, когда функция получила указатель неизвестного происхождения, поведение записи объекта через T*, преобразование T* в U*, а затем чтение объекта через U* было бы эквивалентно написанию объединение через член типа T и чтение как тип U, которое будет стандартно определено в нескольких случаях (например, при доступе к элементам Common Initial Sequence) и для реализации (вместо Undefined) для остальных, Несмотря на то, что программы для использования гарантий СНГ с реальными объектами типа объединения редко встречались, было гораздо чаще использовать тот факт, что указатели на объекты неизвестного происхождения должны вести себя как указатели на членов профсоюза и иметь связанные с ним поведенческие гарантии.

Ответ 17

Союзы велики. Одно умное использование профсоюзов, которые я видел, - использовать их при определении события. Например, вы можете решить, что событие равно 32 бит.

Теперь, в пределах этих 32 бит, вы можете назначить первые 8 бит как идентификатор отправителя события... Иногда вы имеете дело с событием в целом, иногда вы разбираете его и сравниваете его компоненты, союзы дают вам гибкость для обоих.

union Event
{
  unsigned long eventCode;
  unsigned char eventParts[4];
};

Ответ 18

Простой и очень полезный пример:....

Представьте себе:

у вас есть uint32_t array[2] и вы хотите получить доступ к 3-му и 4-му байту в цепочке байтов. вы можете сделать *((uint16_t*) &array[1]). Но это печально нарушает строгие правила псевдонимов!

Но известные компиляторы позволяют сделать следующее:

union un
{
    uint16_t array16[4];
    uint32_t array32[2];
}

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