Когда кто-нибудь будет использовать профсоюз? Это остаток от C-only дней?


133

Я научился, но на самом деле не получить союзы. Каждый текст на C или C ++, который я прохожу, представляет их (иногда мимоходом), но они, как правило, дают очень мало практических примеров того, почему или где их использовать. Когда профсоюзы будут полезны в современном (или даже устаревшем) случае? Мои только два предположения будут программировать микропроцессоры, когда у вас очень ограниченное пространство для работы или когда вы разрабатываете API (или что-то подобное), и вы хотите заставить конечного пользователя иметь только один экземпляр из нескольких объектов / типов в один раз. Эти два предположения даже близко к праву?


31
C / C ++ не является языком. Союзы умеренно полезны в C и в значительной степени бесполезны в C ++. Было бы правильно сказать, что в C ++ они являются «остатком от C ++, основанного на C», но нельзя сказать, что они являются «остатком от C только дней», как если бы C ++ заменял собой C.
R .. GitHub Хватит помогать льду

12
Можете ли вы рассказать, что заменитель союзов в c ++ является, или почему они бесполезны в c ++?
Рассел

3
C ++ заменяет союзы классами и наследованием - союзы в C почти исключительно используются для безопасного для типов полиморфизма. Что-то классы намного лучше в (См. Ответ vz0 о полиморфизме в стиле C)
tobyodavies

6
@R ..: union все еще умеренно полезен в C ++. Смотрите ответы ниже.
Майкл

2
Союзы могут быть чрезвычайно ценными в кишечнике операционной системы или, например, в пакете, который собирает / разбирает звуковые файлы. В таких контекстах они используются множеством разных способов - преобразование данных в байты, низкоуровневый полиморфизм и др. Да, есть другие решения той же проблемы (в основном, приведение между типами указателей), но союзы часто более чистые и лучше самодокументируются.
Hot Licks

Ответы:


105

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

struct my_variant_t {
    int type;
    union {
        char char_value;
        short short_value;
        int int_value;
        long long_value;
        float float_value;
        double double_value;
        void* ptr_value;
    };
};

Тогда вы будете использовать его как:

/* construct a new float variant instance */
void init_float(struct my_variant_t* v, float initial_value) {
    v->type = VAR_FLOAT;
    v->float_value = initial_value;
}

/* Increments the value of the variant by the given int */
void inc_variant_by_int(struct my_variant_t* v, int n) {
    switch (v->type) {
    case VAR_FLOAT:
        v->float_value += n;
        break;

    case VAR_INT:
        v->int_value += n;
        break;
    ...
    }
}

На самом деле это довольно распространенная идиома, особенно в Visual Basic.

Для реального примера см. SDL SDL_Event union . ( фактический исходный код здесь ). В typeверхней части объединения есть поле, и то же поле повторяется в каждой структуре события SDL_ *. Затем, чтобы обработать правильное событие, вам нужно проверить значение typeполя.

Преимущества просты: существует один тип данных для обработки всех типов событий без использования ненужной памяти.


2
Большой! В этом случае, я теперь задаюсь вопросом, почему функция Sdl не была просто реализована как иерархия классов. Это для того, чтобы сделать его совместимым с C, а не только с C ++?
Рассел

12
Классы @Russel C ++ нельзя использовать из программы на C, но структуры / объединения C можно легко получить из C ++, используя блок 'extern "C".
vz0

1
Этот вариант шаблона также часто используется для интерпретаторов языка программирования, например, определение struct objectв github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Адам Розенфилд

1
Потрясающее объяснение. Я всегда знал, что такое союзы, но никогда не видел реальной причины, по которой кто-то может быть настолько сумасшедшим, чтобы использовать их :) Спасибо за пример.
riwalk

@ Stargazer712, поиск кода Google: google.com/…
kagali-san

87

Я нахожу профсоюзы C ++ довольно крутыми. Кажется, что люди обычно думают только о случае использования, когда кто-то хочет изменить значение экземпляра объединения «на месте» (которое, кажется, служит только для экономии памяти или выполнения сомнительных преобразований).

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

Вариант использования 1: хамелеон

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

struct Batman;
struct BaseballBat;

union Bat
{
    Batman brucewayne;
    BaseballBat club;
};

ReturnType1 f(void)
{
    BaseballBat bb = {/* */};
    Bat b;
    b.club = bb;
    // do something with b.club
}

ReturnType2 g(Bat& b)
{
    // do something with b, but how do we know what's inside?
}

Bat returnsBat(void);
ReturnType3 h(void)
{
    Bat b = returnsBat();
    // do something with b, but how do we know what's inside?
}

Похоже, что программист должен быть уверен в типе содержимого данного экземпляра объединения, когда он хочет его использовать. Это дело в функции fвыше. Однако, если функция получит экземпляр объединения в качестве переданного аргумента, как в случае с gвыше, то она не будет знать, что с ним делать. То же самое относится и к функциям, возвращающим экземпляр объединения, смотрите h: как вызывающая сторона узнает, что внутри?

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

Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;

И это самый (не) популярный вариант использования союзов. Другой вариант использования - это когда экземпляр объединения приходит вместе с чем-то, что говорит вам о его типе.

Вариант использования 2: «Приятно познакомиться, я objectиз Class»

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

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

Вариант использования 3:

Как утверждают авторы рекомендации для стандарта ISO C ++ в 2008 году,

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

А теперь пример с диаграммой классов UML:

много композиций для класса А

Ситуация на простом английском языке: у объекта класса A могут быть объекты любого класса из B1, ..., Bn и не более одного каждого типа, причем n - довольно большое число, скажем, по меньшей мере 10.

Мы не хотим добавлять поля (элементы данных) в A следующим образом:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

потому что n может варьироваться (мы могли бы добавить классы Bx в смесь), и потому что это могло бы вызвать беспорядок с конструкторами и потому что объекты A занимали бы много места.

Мы могли бы использовать дурацкий контейнер void*указателей на Bxобъекты с приведениями для их извлечения, но это глупо и так в стиле C ... но что более важно, это оставило бы нам время жизни многих динамически распределенных объектов для управления.

Вместо этого можно сделать следующее:

union Bee
{
    B1 b1;
    .
    .
    .
    Bn bn;
};

enum BeesTypes { TYPE_B1, ..., TYPE_BN };

class A
{
private:
    std::unordered_map<int, Bee> data; // C++11, otherwise use std::map

public:
    Bee get(int); // the implementation is obvious: get from the unordered map
};

Затем, чтобы получить содержимое экземпляра объединения data, вы используете a.get(TYPE_B2).b2и лайки, где aнаходится Aэкземпляр класса .

Это тем более мощно, так как союзы не ограничены в C ++ 11. См. Документ, связанный с выше или этой статьей для деталей.


Это было очень полезно, и серия этой второй статьи была очень информативной. Спасибо.
Андрей

38

Один пример - во встроенной области, где каждый бит регистра может означать что-то другое. Например, объединение 8-разрядного целого числа и структуры с 8 отдельными 1-разрядными битовыми полями позволяет изменять один бит или весь байт.


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

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

хе? Много явных приведений? Мне кажется, простые заявления, как REG |= MASKи REG &= ~MASK. Если это подвержено ошибкам, поместите их в #define SETBITS(reg, mask)и #define CLRBITS(reg, mask). Не полагайтесь на компилятор, чтобы получить биты в определенном порядке ( stackoverflow.com/questions/1490092/… )
Майкл

26

Херб Саттер написал в GOTW около шести лет назад, с акцентом добавил:

«Но не думайте, что союзы являются лишь пережитком прежних времен. Союзы, пожалуй, наиболее полезны для экономии места, позволяя перекрывать данные, и это все еще желательно в C ++ и в современном мире. Например, некоторые из наиболее передовой C ++Стандартные реализации библиотек в мире теперь используют именно эту технику для реализации «оптимизации небольших строк», отличной альтернативы оптимизации, которая повторно использует хранилище внутри самого строкового объекта: для больших строк пространство внутри строкового объекта хранит обычный указатель на динамически выделенный буфер и служебная информация, например размер буфера; для небольших строк одно и то же пространство используется повторно для непосредственного хранения содержимого строки и полного исключения динамического выделения памяти. Для получения дополнительной информации об оптимизации небольших строк (а также о других оптимизациях и пессимизациях строк в значительной степени) см. ... ".

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


23

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

typedef union
{
    struct
    {
        uint8_t a;
        uint8_t b;
        uint8_t c;
        uint8_t d;
    };
    uint32_t x;
} some32bittype;

Затем вы можете получить доступ к 8-битным отдельным частям этого 32-битного блока данных; Тем не менее, подготовьтесь к тому, чтобы быть укушенным порядком байтов.

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

Тем не менее, есть также метод, который является endian-safe:

uint32_t x;
uint8_t a = (x & 0xFF000000) >> 24;

Например, поскольку эта двоичная операция будет преобразована компилятором в правильную последовательность.


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

15

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

  • Предоставить интерфейс с прямым порядком байтов для неизвестного внешнего хоста.
  • Управлять данными с плавающей запятой чужой архитектуры ЦП, например, принимать VAX G_FLOATS из сетевого канала и преобразовывать их в длинные значения IEEE 754 для обработки.
  • Обеспечьте простой доступ к типу более высокого уровня.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

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

  • Экономия места на диске, когда поля зависят от определенных значений:

    class person {  
        string name;  
    
        char gender;   // M = male, F = female, O = other  
        union {  
            date  vasectomized;  // for males  
            int   pregnancies;   // for females  
        } gender_specific_data;
    }
  • Grep включаемые файлы для использования с вашим компилятором. Вы найдете десятки к сотням использования union:

    [wally@zenetfedora ~]$ cd /usr/include
    [wally@zenetfedora include]$ grep -w union *
    a.out.h:  union
    argp.h:   parsing options, getopt is called with the union of all the argp
    bfd.h:  union
    bfd.h:  union
    bfd.h:union internal_auxent;
    bfd.h:  (bfd *, struct bfd_symbol *, int, union internal_auxent *);
    bfd.h:  union {
    bfd.h:  /* The value of the symbol.  This really should be a union of a
    bfd.h:  union
    bfd.h:  union
    bfdlink.h:  /* A union of information depending upon the type.  */
    bfdlink.h:  union
    bfdlink.h:       this field.  This field is present in all of the union element
    bfdlink.h:       the union; this structure is a major space user in the
    bfdlink.h:  union
    bfdlink.h:  union
    curses.h:    union
    db_cxx.h:// 4201: nameless struct/union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:typedef union
    _G_config.h:typedef union
    gcrypt.h:  union
    gcrypt.h:    union
    gcrypt.h:    union
    gmp-i386.h:  union {
    ieee754.h:union ieee754_float
    ieee754.h:union ieee754_double
    ieee754.h:union ieee854_long_double
    ifaddrs.h:  union
    jpeglib.h:  union {
    ldap.h: union mod_vals_u {
    ncurses.h:    union
    newt.h:    union {
    obstack.h:  union
    pi-file.h:  union {
    resolv.h:   union {
    signal.h:extern int sigqueue (__pid_t __pid, int __sig, __const union sigval __val)
    stdlib.h:/* Lots of hair to allow traditional BSD use of `union wait'
    stdlib.h:  (__extension__ (((union { __typeof(status) __in; int __i; }) \
    stdlib.h:/* This is the type of the argument to `wait'.  The funky union
    stdlib.h:   causes redeclarations with either `int *' or `union wait *' to be
    stdlib.h:typedef union
    stdlib.h:    union wait *__uptr;
    stdlib.h:  } __WAIT_STATUS __attribute__ ((__transparent_union__));
    thread_db.h:  union
    thread_db.h:  union
    tiffio.h:   union {
    wchar.h:  union
    xf86drm.h:typedef union _drmVBlank {

5
Тск тск! Два отрицательных голоса и никаких объяснений. Это разочаровывает.
Wallyk

Пример с человеком, который может держать мужчину и женщину, очень плохой дизайн в моих глазах. Почему не человек базового класса, а мужчина и женщина, один из них? Извините, но поиск переменной для определения сохраненного типа в поле данных вручную - плохая идея. Это код c, созданный вручную, никогда не встречавшийся годами. Но никакого негатива, это только моя точка зрения :-)
Клаус

4
Я полагаю, вы получили отрицательные отзывы за союз "кастрированных" или "беременностей". Это немного больно.
Акалтар

2
Да, я думаю, это был темный день.
Wallyk

14

Объединения полезны при работе с данными уровня байтов (низкого уровня).

Одним из моих недавних использований было моделирование IP-адресов, которое выглядит следующим образом:

// Composite structure for IP address storage
union
{
    // IPv4 @ 32-bit identifier
    // Padded 12-bytes for IPv6 compatibility
    union
    {
        struct
        {
            unsigned char _reserved[12];
            unsigned char _IpBytes[4];
        } _Raw;

        struct
        {
            unsigned char _reserved[12];
            unsigned char _o1;
            unsigned char _o2;
            unsigned char _o3;
            unsigned char _o4;    
        } _Octet;    
    } _IPv4;

    // IPv6 @ 128-bit identifier
    // Next generation internet addressing
    union
    {
        struct
        {
            unsigned char _IpBytes[16];
        } _Raw;

        struct
        {
            unsigned short _w1;
            unsigned short _w2;
            unsigned short _w3;
            unsigned short _w4;
            unsigned short _w5;
            unsigned short _w6;
            unsigned short _w7;
            unsigned short _w8;   
        } _Word;
    } _IPv6;
} _IP;

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

3
Кроме того, очень часто можно увидеть, что это используется способом, который не гарантирует выравнивание, что является неопределенным поведением.
Mooing Duck

10

Пример, когда я использовал союз:

class Vector
{
        union 
        {
            double _coord[3];
            struct 
            {
                double _x;
                double _y; 
                double _z;
            };

        };
...
}

это позволяет мне получить доступ к моим данным в виде массива или элементов.

Я использовал объединение, чтобы разные термины указывали на одно и то же значение. При обработке изображений, работаю ли я над столбцами, шириной или размером в направлении X, это может сбить с толку. Чтобы решить эту проблему, я использую объединение, чтобы знать, какие описания идут вместе.

   union {   // dimension from left to right   // union for the left to right dimension
        uint32_t            m_width;
        uint32_t            m_sizeX;
        uint32_t            m_columns;
    };

    union {   // dimension from top to bottom   // union for the top to bottom dimension
        uint32_t            m_height;
        uint32_t            m_sizeY;
        uint32_t            m_rows;
    };

12
Обратите внимание, что хотя это решение работает на большинстве наблюдаемых платформ, установка значений _x, _y, _z и доступ к _coord является неопределенным поведением. Основная цель союзов - сохранение пространства. Вы должны получить доступ к тому же элементу объединения, который вы ранее установили.
worieux

1
это то, как я использую это тоже, хотя я использую std :: array для координат координат и некоторые static_asserts
Виктор

1
Этот код нарушает строгие правила псевдонимов и не должен быть рекомендован.
Уолтер

Есть ли способ улучшить профсоюз, чтобы это было надежно?
Андрей

8

Союзы обеспечивают полиморфизм в C.


18
Я думал, void*сделал это ^^

2
@ user166390 Полиморфизм использует один и тот же интерфейс для манипулирования несколькими типами; void * не имеет интерфейса.
Алиса

2
В Си полиморфизм обычно реализуется через непрозрачные типы и / или указатели на функции. Я понятия не имею, как или почему вы бы использовали профсоюз для достижения этой цели. Это звучит как действительно плохая идея.
Лундин

7

Блестящее использование объединения - выравнивание памяти, которое я нашел в исходном коде PCL (Point Cloud Library). Одна структура данных в API может быть ориентирована на две архитектуры: ЦП с поддержкой SSE, а также ЦП без поддержки SSE. Например, структура данных для PointXYZ

typedef union
{
  float data[4];
  struct
  {
    float x;
    float y;
    float z;
  };
} PointXYZ;

3 поплавка дополняются дополнительным поплавком для выравнивания SSE. Таким образом, для

PointXYZ point;

Пользователь может получить доступ к point.data [0] или point.x (в зависимости от поддержки SSE) для доступа, скажем, к координате x. Больше похожих подробностей об использовании можно найти по следующей ссылке: Документация PCL Типы PointT


7

unionКлючевое слово, в то время как до сих пор используется в C ++ 03 1 , в основном остаток дней C. Наиболее вопиющая проблема заключается в том, что он работает только с POD 1 .

Идея объединения, однако, все еще присутствует, и библиотеки Boost действительно имеют класс, подобный объединению:

boost::variant<std::string, Foo, Bar>

Который имеет большинство преимуществ union(если не все) и добавляет:

  • способность правильно использовать не POD типы
  • безопасность статического типа

На практике было продемонстрировано, что оно было эквивалентно комбинации union+ enum, и было отмечено, что оно было таким же быстрым (хотя boost::anyэто больше относится к области dynamic_cast, поскольку оно использует RTTI).

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


Это больше не верно в более поздних версиях c ++. См. Ответ Джрисалы, например.
Андрей

@Andrew: Я обновил ответ, упомянув, что C ++ 11 с неограниченными объединениями позволял хранить типы с деструкторами в union. Я по-прежнему придерживаюсь своей позиции, что вам действительно лучше использовать помеченные союзы, например, boost::variantчем пытаться использовать союзы самостоятельно. Слишком много неопределенного поведения вокруг профсоюзов, что ваши шансы сделать все правильно, безнадежно.
Матье М.

3

Из статьи Википедии о профсоюзах :

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

Одна распространенная идиома программирования на C использует объединения для выполнения того, что в C ++ называется reinterpret_cast, путем присвоения одному полю объединения и чтения из другого, как это делается в коде, который зависит от необработанного представления значений.


2

В первые дни существования C (например, как было задокументировано в 1974 г.) все структуры имели общее пространство имен для своих членов. Каждое имя члена было связано с типом и смещением; если бы "wd_woozle" был "int" со смещением 12, то данный указатель pлюбого типа структуры p->wd_woozleбыл бы эквивалентен *(int*)(((char*)p)+12). Язык требовал, чтобы все члены всех типов структур имели уникальные имена, за исключением того, что он явно разрешал повторное использование имен членов в тех случаях, когда каждая структура, в которой они использовались, воспринимала их как общую начальную последовательность.

Тот факт, что типы структур могут использоваться беспорядочно, позволил им вести себя так, как будто они содержат перекрывающиеся поля. Например, даны определения:

struct float1 { float f0;};
struct byte4  { char b0,b1,b2,b3; }; /* Unsigned didn't exist yet */

Код может объявить структуру типа «float1», а затем использовать «members» b0 ... b3 для доступа к отдельным байтам в нем. Когда язык был изменен так, чтобы каждая структура получала отдельное пространство имен для своих членов, код, который полагался на возможность доступа к вещам несколькими способами, сломался. Значения разделения пространств имен для различных типов структур было достаточно, чтобы потребовать изменения такого кода, чтобы приспособить его, но ценность таких методов была достаточной, чтобы оправдать расширение языка для его дальнейшей поддержки.

Код , который был написан для использования возможности доступа к памяти в пределах , struct float1как если бы это было struct byte4можно сделать , чтобы работать в новом языке путем добавления объявления: union f1b4 { struct float1 ff; struct byte4 bb; };, объявляя объекты как тип , union f1b4;а не struct float1, и заменяя доступы к f0, b0, b1и т.д. . с ff.f0, bb.b0, bb.b1и т.д. в то время как есть более эффективные способы такой код мог быть поддерживается, unionподход был по крайней мере , несколько работоспособным, по крайней мере , с C89 эпохи интерпретации правил наложения спектров.


1

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

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


1

Одно недавнее повышение уже объединенной важности объединения было дано Строгим правилом псевдонимов, введенным в недавней версии стандарта C.

Вы можете использовать союзы, чтобы печатать, не нарушая стандарт Си.
Эта программа имеет неопределенное поведение (потому что я предположил, что floatи unsigned intимеет ту же длину), но не неопределенное поведение (см. Здесь ).

#include <stdio.h> 

union float_uint
{
    float f;
    unsigned int ui;
};

int main()
{
    float v = 241;
    union float_uint fui = {.f = v};

    //May trigger UNSPECIFIED BEHAVIOR but not UNDEFINED BEHAVIOR 
    printf("Your IEEE 754 float sir: %08x\n", fui.ui);

    //This is UNDEFINED BEHAVIOR as it violates the Strict Aliasing Rule
    unsigned int* pp = (unsigned int*) &v;

    printf("Your IEEE 754 float, again, sir: %08x\n", *pp);

    return 0;
}

Правила доступа типа не только в «последних» версиях Стандарта. Каждая версия C включает в себя, по сути, одни и те же правила. Что изменилось, так это то, что компиляторы использовали сноску «Цель этого списка - указать те обстоятельства, при которых объект может или не может иметь псевдоним». как указание на то, что правило не предназначалось для применения в случаях, когда псевдонимы не были записаны , но теперь они рассматривают его как приглашение переписать код для создания псевдонимов там, где их еще не было.
суперкат

1

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

struct Number{
enum NumType{int32, float, double, complex}; NumType num_t;
union{int ival; float fval; double dval; ComplexNumber cmplx_val}
}

Таким образом, вы экономите память и, что более важно, вы избегаете любых динамических распределений для, возможно, экстремального количества (если вы используете много определенных во время выполнения чисел) небольших объектов (по сравнению с реализациями через наследование / полиморфизм классов). Но что более интересно, вы все еще можете использовать мощь полиморфизма C ++ (например, если вы фанат двойной диспетчеризации;) с этим типом структуры. Просто добавьте указатель «фиктивного» интерфейса на родительский класс всех типов чисел в качестве поля этой структуры, указывая на этот экземпляр вместо / в дополнение к необработанному типу, или используйте старые добрые указатели на функции Си.

struct NumberBase
{
virtual Add(NumberBase n);
...
}
struct NumberInt: Number
{
//implement methods assuming Number's union contains int
NumberBase Add(NumberBase n);
...
}
struct NumberDouble: Number
{
 //implement methods assuming Number's union contains double
 NumberBase Add(NumberBase n);
 ...
}
//e.t.c. for all number types/or use templates
struct Number: NumberBase{
 union{int ival; float fval; double dval; ComplexNumber cmplx_val;}
 NumberBase* num_t;
 Set(int a)
 {
 ival=a;
  //still kind of hack, hope it works because derived classes of   Number    dont add any fields
 num_t = static_cast<NumberInt>(this);
 }
}

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


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

0

С http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :

Использование союза мало и далеко друг от друга. На большинстве компьютеров размер указателя и целого числа обычно одинаковы - это потому, что оба обычно помещаются в регистр в ЦП. Поэтому, если вы хотите выполнить быстрое и грязное приведение указателя к int или другим способом, объявите объединение.

union intptr {   int i;   int * p; }; 
union intptr x; x.i = 1000; 
/* puts 90 at location 1000 */ 
*(x.p)=90; 

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

struct head {   int id;   int response;   int size; }; struct msgstring50 {    struct head fixed;    char message[50]; } struct

struct msgstring80 {структура головы исправлена; символьное сообщение [80]; }
struct msgint10 {структура головы исправлена; int сообщение [10]; } struct msgack {struct head fixed; int ok; } union messagetype {
struct msgstring50 m50; struct msgstring80 m80; struct msgint10 i10; struct msgack ack; }

На практике, хотя союзы имеют одинаковый размер, имеет смысл отправлять только значимые данные, а не потерянное пространство. Размер msgack составляет всего 16 байтов, а msgstring80 - 92 байта. Поэтому, когда переменная messagetype инициализируется, у нее устанавливается поле размера, соответствующее ее типу. Это может затем использоваться другими функциями для передачи правильного количества байтов.


0

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

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

union u_tag {
     int ival;
     float fval;
     char  *sval;
} u;

Переменная u будет достаточно большой, чтобы вместить самый большой из трех типов; конкретный размер зависит от реализации. Любой из этих типов может быть назначен вам и затем использован в выражениях, если использование является последовательным

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.