Объединения
Рассмотрим таблицу имен, в которой каждый элемент содержит имя и его значение. Значение может задаваться либо строкой, либо целым числом:
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. Из этого следует, что все члены объединения вместе занимают такой же объем памяти, какой занимает наибольший член объединения.
Надежный способ работы с объединением заключается в том, чтобы выбирать значение с помощью того же самого члена, который его записывал. Однако, в больших программах трудно гарантировать, что объединение используется только таким способом, а в результате использования не того члена объединения могут возникать трудно обнаруживаемые ошибки. Но можно встроить объединение в такую структуру, которая обеспечит правильную связь между значением поля типа и текущим типом члена объединения.
Иногда объединения используют для "псевдопреобразований" типа (в основном на это идут программисты, привыкшие к языкам, в которых нет средств преобразования типов, и в результате приходится обманывать транслятор). Приведем пример такого "преобразования" int в int* на машине VAX, которое достигается простым совпадением разрядов:
struct fudge { union { int i; int* p; }; };
fudge a; a.i = 4095; int* p = a.p; // некорректное использование
В действительности это вовсе не преобразование типа, т.к. на одних машинах int и int* занимают разный объем памяти, а на других целое не может размещаться по адресу, задаваемому нечетным числом. Такое использование объединений не является переносимым, тогда как существует переносимый способ задания явного преобразования типа.
Иногда объединения используют специально, чтобы избежать преобразования типов. Например, можно использовать fudge, чтобы узнать, как представляется указатель 0:
fudge.p = 0; int i = fudge.i; // i необязательно должно быть 0
Объединению можно дать имя, то есть можно сделать его полноправным типом. Например, fudge можно описать так:
union fudge { int i; int* p; };
и использовать (некорректно) точно так же, как и раньше. Вместе с тем, поименованные объединения можно использовать и вполне корректным и оправданным способом.