|
|
Рассмотрим таблицу имен, в которой каждый элемент содержит имя и его значение. Значение может задаваться либо строкой, либо целым числом:
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).
| |