Бьерн Страуструп - Язык программирования С++. Главы 2-4 - Объединения

ОГЛАВЛЕНИЕ

2.6.2. Объединения

Рассмотрим таблицу имен, в которой каждый элемент содержит имя и его значение. Значение может задаваться либо строкой, либо целым числом:
            struct entry {
               char* name;
               char  type;
               char* string_value;  // используется если type == 's'
               int   int_value;     // используется если type == 'i'
            };
            void print_entry(entry* p)
            {
              switch(p->type) {
              case 's':
                   cout << p->string_value;
                   break;
              case 'i':
                   cout << p->int_value;
                   break;
              default:
                   cerr << "type corrupted\n";
                   break;
              }
            }
Поскольку переменные string_value и int_value никогда не могут использоваться одновременно, очевидно, что часть памяти пропадает впустую. Это можно легко исправить, описав обе переменные как члены объединения, например, так:
             struct entry {
                  char* name;
                  char  type;
                  union {
                     char* string_value; // используется если type == 's'
                     int   int_value;    // используется если type == 'i'
                  };
             };

Теперь гарантируется, что при выделении памяти для entry члены string_value и int_value будут размещаться с одного адреса, и при этом не нужно менять все части программы, работающие с entry. Из этого следует, что все члены объединения вместе занимают такой же объем памяти, какой занимает наибольший член объединения.

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

Иногда объединения используют для "псевдопреобразований" типа (в основном на это идут программисты, привыкшие к языкам, в которых нет средств преобразования типов, и в результате приходится обманывать транслятор). Приведем пример такого "преобразования"  int в int* на машине VAX, которое достигается простым совпадением разрядов:

         struct fudge {
              union {
                int  i;
                int* p;
              };
         };

         fudge a;
         a.i = 4095;
         int* p = a.p;    // некорректное использование

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

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

         fudge.p = 0;
         int i = fudge.i;    // i необязательно должно быть 0
Объединению можно дать имя, то есть можно сделать его полноправным типом. Например, fudge можно описать так:
         union fudge {
               int  i;
               int* p;
         };
и использовать (некорректно) точно так же, как и раньше. Вместе с тем, поименованные объединения можно использовать и вполне корректным и оправданным способом (см. $$5.4.6).