11.8 Unions
A union is a variable that may hold (at di erent times) objects of di erent types and sizes, with the compiler keeping track of size and alignment requirements. Unions provide a way to manipulate di erent kinds of data in a single area of storage, without embedding any machine-dependent information in the program [KR88, page 147].
The declaration of a union type is similar to the declaration of a struct type. For example,
union Utype {
int ival;
float fval;
char *sval;
};
union Utype x, y, z; /* Define variables of type Utype. */
Accessing members of a union type is also the same as for structures, with the . member operator for union objects and the -> operator for pointers to union objects.
However, the behaviour of union variables is quite di erent to structures. Where a struct defines a group of related variables and provides storage for all of its members, a union provides storage for a single variable, which may be one of several types. In the above example, the compiler will allocate su cient memory to store the largest of the types int, float, and char *. At any given time, a Utype variable holds a value for one of the three possible types, and it is the programmers responsibility to keep track of which type that might be. The type retrieved must be the same as the type most recently stored, and the result of retrieving a di erent type is implementation dependent.
union Utype x;
x.fval = 56.4; /* x holds type float. */ printf("%f\n", x.fval); /* OK. */
printf("%d\n", x.ival); /* Implementation dependent. */
Unions are usually used for one of three purposes. The first is to create a “variant” array—an array that can hold heterogeneous elements. This can be performed with a reasonable degree of type safety by wrapping the union within a structure which records the union variable’s current type. For example,
typedef union { /* Heterogeneous type. */
int ival;
float fval;
} Utype;
enum { INT, FLOAT }; /* Define type tags. */
typedef struct {
int type; /* Tag for the current stored type. */ Utype val; /* Storage for variant type. */
} VariantType;
VariantType array[50]; /* Heterogeneous array. */ array[0].val.ival = 56; /* Assign value. */
array[0].type = INT; /* Mark type. */
...
for (i = 0; i < sizeof(array)/sizeof(array[0]); ++i) /* Print array. */ if (array[i].type == INT)
printf("%d\n", array[i].val.ival);
else if (array[i].type == FLOAT)
printf("%f\n", array[i].val.fval);
else
printf("Unknown type\n");
Checking for the correct type remains the programmer’s responsibility, but encoding the variable type in a structure eases the pain of recording the current state.
The second use of a union is to enforce the alignment of a variable to a particular address boundary. This is a valuable property for implementing memory allocation functions. And the third key use of a union is to get “under the hood” of C’s type system to discover something about the computer’s underlying data representation. For example, to print the representation of a floating-point number, one might use the following function (assuming int and float are both four-bytes).
void print_representation(float f)
/* Print internal representation of a float (adapted from H&S page 145). */
{
union { float f; int i; } fi = f;
printf("The representation of %e is %#x\n", fi.f, fi.i);
}
Both the second and third uses of unions described here are advanced topics, and a more complete discussion is beyond the scope of this text.
No comments:
Post a Comment