Bit-fields can be used to allow flags or other integer values with small ranges to be packed together to save storage space. When used in structure members, bit fields can improve storage efficiency.
In portable code, do not depend upon the allocation order of bit-fields in memory. Of course, in machine-specific non-portable code one knows exactly how the bit-fields are laid out, and the internal details can be inspected with bitwise operations.
Consider the representation of time-of-day in hours, minutes, seconds and milliseconds. Bit-fields provide one way to represent such times:
Compilers will typically allocate consecutive bit-field structure members to the same int
-sized dword, as long as they fit into that completely into that dword. However, the order of allocation within a word is different in different implementations. This is known as the implementation's endianness. Big endian machines store the most significant bit at the lowest address versus little endian implementations, which store the most significant bit at the highest address. Depending of the order bits within a word can lead to different calculations on different implementations.
Consider the following structure made up of four 8-bit bit field members.
Code Block |
---|
struct bf |
Code Block |
typedef struct time_day { unsigned h1m1 : 2; {0:2}8; unsigned h2 unsigned m1 unsigned m2 : unsigned s18; unsigned s2 m3 : unsigned f18; unsigned f2 m4 : unsigned f3 {0:9} {0:5) {0:9) {0:5) {0:9) {0:9} {0:9} {0:9} } TIME_DAY8; };; /* 32 bits total */ |
The last millisecond of the day is
23:59:59.999 (hh:mm:ss.fff)
Each member (bit-field) is declared to be unsigned (int); this is the only bit-field type that is guaranteed to be portable to all current compilers. Each member is declared to have only as many bits as are necessary to represent the possible digits at its position in the time representation. Representing h1 (first digit of hours) takes only two bits to represent the possible values (0, 1, and 2). And the largest members need only four bits to represent ten digits, 0 through 9. The total number of bits is 32.
Consecutive bit-field members are allocated by the compiler to the same int
-sized word, as long as they fit completely. Consequently, on a 32-bit machine, a TIME_DAY
object occupies exactly one {int}}-sized word. Such an exact fit is rare, however. Add another member such as "day-of-year" to the structure, and the nice size-fitting property disappears. Consequently, bit-fields are useful for storage-saving only if they occupy most or all of the space of an int
, and if the storage-saving property is to be reasonably portable, they must occupy most of the space in a 32-bit integer.
The order of allocation within a word is different in different implementations. Some implementations are "right-to-left": the first member occupies the low-order position of the word. Following the convention that the low-order bit of a word is on the right, the right-to-left allocation would look like this:
Code Block |
---|
f3 f2 f1 s2 s1 m2 ml h2 h1
|
Most other implementations are "left-to-right":
Code Block |
---|
h1 h2 ml m2 s1 s2 f1 f2 f3
|
A union provides a convenient way to say what is going on:
Code Block |
---|
typedef union time_overlay { /* MACHINE DEPENDENT */
struct time_day time_as_fields;
long time_as_long;
} TIME_OVERLAY;
TIME_OVERLAY time_port;
|
This allows bitwise operations like time_port.time_as_long & 0xF00
as well as providing access via bit-field names like time_port.time_as_fields.h1
.
Specifying a field size of zero causes any subsequent allocations to begin on a new word boundary. Un-named bit-fields are allowed; they occupy space but are inaccessible, which is useful for padding within a structure.
Because most C machines do not support bit-addressing, the "address-of" (&) operator is not allowed upon bit-field members.
Aside from these complications, bit-fields can be treated just like any other structure member. The following declaration
Code Block |
---|
#include "time_day.h"
struct time_day last_msec = {2, 3, 5, 9, 5, 9, 9, 9, 9};
/* initializes last_msec to the last millisecond of the day. */
struct time_day now;
/* ... */
if (now.h1 == 0 || (now.h1 == 1 && now.h2 < 2))
|
tests whether now is less than noon.
...
/* ... */
|
Little endian implementations will allocate struct bf
as:
Code Block |
---|
m4 m3 m2 m1
|
Conversely, big endian endian implementations will allocate struct bf
as:
Code Block |
---|
m1 m2 m3 m4
|
Risk Assessment
Making invalid assumptions about the type of a bit-field or its layout can result in unexpected program flow.
...