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



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).

 
« Предыдущая статья   Следующая статья »