Unions and bit-fields are minor extensions to the structure syntax. Like structures, unions and bit-fields have named fields, but they treat them differently. Bit-fields allow programmers to specify each field's size measured in bits. Unions can only save one field at a time, allocating only enough space to save the largest and "wasting" space when saving the smaller ones. Programs can use bit-fields and unions independently or jointly to perform some data conversions. However, bit-fields and any data conversion based on them are system-dependent and not portable across operating systems, hardware, or both.
General-purpose processors or CPUs - the ones running the familiar desktop operating systems - only support byte-addressable memory. Consequently, a byte is the smallest memory unit they can directly access, and larger data types are always aligned on byte boundaries. Bit-fields are one way programmers can work around these limitations by specifying fields with arbitrary bit lengths. This mechanism still doesn't qualify as direct access because additional access operations occur outside the programmer's view. The ending bits remain unused if the last bit-field doesn't end on a byte boundary. Finally, the fields are typically unsigned because programs are generally concerned with only the individual bits.
// bit-field used to unpack the st_mode in field in a _stat structure struct modes { unsigned others : 3; // permissions for everyone else unsigned group : 3; // group permissions unsigned user : 3; // file owner permissions unsigned type : 7; // file type: directory, regular file, pipe, device, etc. };
More about bit-fields after a discussion of unions.
The syntax specifying a union is similar to the syntax specifying a structure, only replacing struct
with union
. However, that small change in syntax results in a large change in the behavior of the instantiated objects.
![]() |
![]() |
(a) | (b) |
Although newer ANSI standards have increased the number of automatic type promotions C++ can perform, it's still described as a strongly typed language, meaning that it often can't assign or pass one data type to a variable of a different type. Although rare, occasionally, we need a variable that can violate or circumvent the strong typing rule by holding data of different types at different times. Unions solve this problem when typecasting can't convert between some of the types, or when a cast might cause a truncation error, or when there isn't a conversion function between some of the types.
union number { char c; int i; double d; }; |
number my_number; my_number.c = 'A'; ... my_number.i = 42; ... my_number.d = 3.14259; ... |
(a) | (b) |
By joining bit-fields and unions, programmers can efficiently pack and unpack encoded data. The combination circumvents C++'s strong typing rules and allows the program to access bit groups smaller than eight bits. Two concepts are crucial for understanding the packing and unpacking process. First, the compiler uses the data's type to determine how to interpret its bits. Second, although a union may have many fields, they all occupy the location in memory.
struct modes { unsigned others : 3; unsigned group : 3; unsigned user : 3; unsigned type : 7; }; |
union map { unsigned short statmode; modes convert; }; |
(a. bit-field) | (b. union) |
map mapper; // i mapper.statmode = data; // ii cout << mapper.convert.user << endl; // iii cout << mapper.convert.group << endl; cout << mapper.convert.others << endl; |
![]() |
(c) | (d) |
Please see stat.cpp for the complete program.