Язык программирования C++ от Страуструпа

       

Объединения


Рассмотрим таблицу имен, в которой каждый элемент содержит имя и его значение. Значение может задаваться либо строкой, либо целым числом:

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


Содержание раздела