Объединения
Рассмотрим проектирование символьной таблицы, в которой каждый элемент содержит имя и значение, и значение может быть либо строкой, либо целым:
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\n"; break; } }
Поскольку string_value и int_value никогда не могут использоваться одновременно, ясно, что пространство пропадает впустую. Это можно легко исправить, указав, что оба они должны быть членами union (объединения); например, так:
struct entry { char* name; char type; union { char* string_value; // используется если type == "s" int int_value; // используется если type == "i" }; };
Это оставляет всю часть программы, использующую entry, без изменений, но обеспечивает, что при размещении entry string_value и int_value имеют один и тот же адрес. Отсюда следует, что все члены объединения вместе занимают лишь столько памяти, сколько занимает наибольший член.
Использование объединений таким образом, чтобы при чтении значения всегда применялся тот член, с применением которого оно записывалось, совершенно оптимально. Но в больших программах непросто гарантировать, что объединения используются только таким образом, и из-за неправильного использования могут появляться трудно уловимые ошибки. Можно капсулизировать объединение таким образом, чтобы соответствие между полем типа и типами членов было гарантированно правильным (см. этот раздел).
Объединения иногда используют для "преобразования типов" (это делают главным образом программисты, воспитанные на языках, не обладающих средствами преобразования типов, где жульничество является необходимым). Например, это "преобразует" на VAX"е int в int*, просто предполагая побитовую эквивалентность:
struct fudge { union { int i; int* p; }; };
fudge a; a.i = 4096; int* p = a.p; // плохое использование
Но на самом деле это совсем не преобразование: на некоторых машинах int и int* занимают неодинаковое количество памяти, а на других никакое целое не может иметь нечетный адрес. Такое применение объединений непереносимо, а есть явный способ указать преобразование типа (см. этот пункт).
Изредка объединения умышленно применяют, чтобы избежать преобразования типов. Можно, например, использовать fudge, чтобы узнать представление указателя 0:
fudge.p = 0; int i = fudge.i; // i не обязательно должно быть 0
Можно также дать объединению имя, то есть сделать его полноправным типом. Например, fudge можно было бы описать так:
union fudge { int i; int* p; };
и использовать (неправильно) в точности как раньше. Имеются также и оправданные применения именованных объединений; см. этот раздел.
1 2
8 8 8
|