Объединения

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

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 имеют один и тот же адрес. Отсюда следует, что все члены объединения вместе занимают лишь столько памяти, сколько занимает наибольший член.

Использование объединений таким образом, чтобы при чтении значения всегда применялся тот член, с применением которого оно записывалось, совершенно оптимально. Но в больших программах непросто гарантировать, что объединения используются только таким образом, и из-за неправильного использования могут появляться трудно уловимые ошибки. Можно капсулизировать объединение таким образом, чтобы соответствие между полем типа и типами членов было гарантированно правильным (#5.4.6).

Объединения иногда используют для "преобразования типов" (это делают главным образом программисты, воспитанные на языках, не обладающих средствами преобразования типов, где жульничество является необходимым). Например, это "преобразует" на VAX'е int в int*, просто предполагая побитовую эквивалентность:

struct fudge { union { int i; int* p; }; }; fudge a; a.i = 4096; 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.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: