Коли хто-небудь би використовував союз? Це залишок від C-днів лише?


133

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


31
C / C ++ - це не мова. Профспілки помірно корисні для С та значною мірою марні для С ++. Було б правильно сказати, що в C ++ вони є "залишком від C ++, заснованого на C", але не сказати, що вони "залишок від C тільки днів", як ніби C ++ випереджає C.
R .. GitHub ЗАСТОСУЙТЕ ДОПОМОГА

12
Чи можете ви детальніше розповісти про те, що замінює c ++ профспілками, або чому вони марні в c ++?
Рассел

3
Замінником C ++ для спілок є класи та успадкування - об'єднання в C майже виключно використовуються для безпечного типу поліморфізму. Щось на уроках набагато краще. (Дивіться відповідь
vz0 на

6
@R ..: Союз все ще помірно корисний для C ++. Відповіді дивіться нижче.
Майкл

2
Союзи можуть бути надзвичайно цінними в кишках операційної системи або, наприклад, у пакеті, який збирає / розбирає звукові файли. У таких контекстах вони використовуються декількома різними способами - перетворення даних / ендіанів, поліморфізм низького рівня тощо. Так, є інші рішення цієї ж проблеми (в основному кастинг між типами вказівників), але спілки часто чистіші і краще самодокументують.
Гарячі лизання

Відповіді:


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_Event SDL . ( фактичний вихідний код тут ). У typeверхній частині об'єднання є поле, і те саме поле повторюється в кожній структурі події SDL_ *. Потім для обробки правильної події вам потрібно перевірити значення typeполя.

Переваги прості: є один єдиний тип даних для обробки всіх типів подій без використання зайвої пам'яті.


2
Чудово! У цьому випадку мені зараз цікаво, чому функція Sdl не була просто реалізована як ієрархія класів. Це зробити так, щоб це було C сумісним, а не лише C ++?
Рассел

12
Класи @Russel C ++ не можна використовувати з програми C, але C структури / об'єднання можна легко отримати доступ із C ++ за допомогою блоку "зовнішній" 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об’єкти з кастами, щоб отримати їх, але це нерозумно і так у стилі С ... але ще важливіше, що залишило б нам життя багатьох динамічно виділених об'єктів для управління.

Натомість можна зробити це:

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*s, або маски та зрушення.
бта

га? Багато явних ролей? Мені здається простими твердженнями, як REG |= MASKі REG &= ~MASK. Якщо до цього є схильні помилки, то поставте їх у #define SETBITS(reg, mask)та #define CLRBITS(reg, mask). Не покладайтеся на компілятор, щоб отримати біти в певному порядку ( stackoverflow.com/questions/1490092/… )
Michael

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-бітного блоку даних; однак, підготуйтеся до того, що потенційно може бути покусаний витримкою.

Це лише один гіпотетичний приклад, але щоразу, коли ви хочете розділити дані в полі на такі складові частини, ви можете використовувати об'єднання.

Однак, існує також безпечний для людей спосіб:

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;
    }
  • Обрізайте файли включення для використання у вашому компіляторі. Ви знайдете десятки до сотень застосувань 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

Приклад з людиною, яка може вмістити чоловіка і жінку, дуже поганий дизайн в моїх очах. Чому б не базовий клас людини, а чоловік та жінка, який його похідно? Вибачте, але шукати змінну вручну для визначення збереженого типу в полі даних aa взагалі погана ідея. Це ручний с код, який ніколи не бачили роками. Але жодної негативної реакції, це лише моя точка зору :-)
Клаус

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
Майте на увазі, що доступ до такої сировини не є стандартною, і може працювати не так, як очікувалося у всіх компіляторів.
NOS

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 є невизначеною поведінкою. Основне призначення спілок - збереження простору. Ви повинні отримати доступ до того самого елемента з'єднання, який ви встановили раніше.
anxieux

1
ось як я і його використовую, через те, що я використовую std :: масив forr, а також деякі static_asserts
Viktor Sehr

1
Цей код порушує суворі правила псевдоніму, і його не рекомендується рекомендувати.
Вальтер

Чи може бути спосіб вдосконалити союз таким, щоб це було надійно зробити?
Андрій

8

Профспілки забезпечують поліморфізм С.


18
Я думав void*, що це зробив ^^

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

2
У C поліморфізм зазвичай реалізується через непрозорі типи та / або функціональні покажчики. Я не маю уявлення, як і чому ви б використовували союз для досягнення цього. Це звучить як справді погана ідея.
Лундін

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 ++. Дивіться, наприклад, відповідь jrsala.
Андрій

@Andrew: Я оновив відповідь, зазначивши, що C ++ 11, з необмеженими об'єднаннями, дозволяв зберігати типи з деструкторами в союзі. Я до сих пір відстоюю свою позицію, що вам справді набагато краще використовувати позначені союзи, такі як boost::variantнамагатися використовувати спілки самостійно. Існує занадто багато невизначеної поведінки навколо профспілок, щоб ваші шанси виправити це неправильно.
Матьє М.

3

З статті Вікіпедії про спілки :

Основна корисність об'єднання полягає в економії простору , оскільки він забезпечує спосіб зберігання багатьох різних типів в одному просторі. Профспілки також забезпечують сирий поліморфізм . Однак перевірки типів не відбувається, тому програміст повинен бути впевнений, що до відповідних полів доступ до різних контекстів. Відповідне поле змінної об'єднання, як правило, визначається станом інших змінних, можливо, у доданій структурі.

Одна загальна ідіома програмування на C використовує об'єднання для виконання того, що C ++ називає переосмисленим_cast, присвоюючи одному полі об'єднання та читаючи з іншого, як це робиться в коді, який залежить від необробленого подання значень.


2

У перші дні С (наприклад, як це було зафіксовано у 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.

Ви можете використовувати профспілки для введення покарань без порушення стандарту 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 містить по суті однакові правила. Що змінилося, це те, що компілятори, які використовувались для розгляду виноски, "Метою цього списку є конкретизація тих обставин, за яких об'єкт може бути, а може і не бути псевдонімом". як вказівка ​​на те, що правило не повинно застосовуватися у випадках, які не включають в себе псевдонім як написано , але тепер вони трактують це як запрошення переписати код, щоб створити псевдонім там, де його ще не було.
supercat

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);
 }
}

тому ви можете використовувати поліморфізм замість перевірки типу з перемикачем (тип) - з ефективної пам’яттю реалізацією (без динамічного розподілу дрібних об’єктів) - якщо вам це потрібно, звичайно.


Це може бути корисно при створенні динамічної мови. Проблема, яку я думаю, що вона вирішить, - це зміна змінної невідомого типу в масі без здійснення цієї модифікації N разів. Макроси для цього жахливі, а шаблонувати практично неможливо.
Андрій

0

Від http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :

Використання об'єднання мало і далеко між ними. На більшості комп'ютерів розмір вказівника та int зазвичай однаковий - це тому, що обидва зазвичай вписуються в реєстр в процесорі. Тож якщо ви хочете зробити швидкий і брудний відкидання покажчика на 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 {зафіксована структура голови; char повідомлення [80]; }
struct msgint10 {fix head fix; int повідомлення [10]; } struct msgack {зафіксована глава голови; int ok; } союзний
обмін повідомленнями { 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 буде достатньо великою, щоб вмістити найбільший з трьох типів; конкретний розмір залежить від реалізації. Будь-який із цих типів може бути призначений u і потім використаний у виразах, якщо використання є послідовним

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.