12.5 Bit
Members
or Packed Structures
So far all the structures you've been using have been
unpacked
.
Packed structures
allow you to declare structures in a way that takes up a minimum of storage. For example, the following structure takes up 6 bytes (using a 16-bit compiler):
struct item {
unsigned int list; // True if item is in the list
unsigned int seen; // True if this item has been seen
unsigned int number; // Item number
};
The storage layout for this structure can be seen in Figure 12-3. Each structure uses 6 bytes of storage (2 bytes for each integer).
Figure 12-3. Unpacked structure
However, the members
list
and
seen
can have only two values, 0 and 1, so only 1 bit is needed to represent them. You never plan on having more than 16383 items (0x3fff or 14 bits). You can redefine this structure using bit members, so that it takes only 2 bytes, by following each member with a
colon
and the number of bits to be used for that member:
struct item {
unsigned int list:1; // True if item is in the list
unsigned int seen:1; // True if this item has been seen
unsigned int number:14; // Item number
};
In this example, you tell the compiler to use 1 bit for
list
, 1 bit for
seen
and 14 bits for
number
. Using this method you can pack data into only 2 bytes, as seen in Figure 12-4.
Figure 12-4. Packed structure
You can add a bit field only to an
int
or
enum
variable. It doesn't work on floating-point members, strings, or other complex types.
Packed structures should be used with care. The machine code to extract data from bit members is relatively large and slow. Unless storage is a problem, packed structures should not be used.
Also, the C++ standard does not define how packing must be implemented. The compiler is free to pack things together in any order it wants to. There is no guarantee that our structure will be stored in two 8-bit bytes. (Some cheap compilers treat packed structures the same an normal structures and leave everything unpacked. This is legal under the C++ standard.)
In Chapter 11, you needed to store character data and five status flags for 8,000
characters
. In this case, using a different byte for each flag would eat up a lot of storage (five bytes for each incoming character). You used bitwise operations to pack the five flags into a single byte. Alternatively, a packed structure could have accomplished the same thing:
struct char_and_status {
char character; // Character from device
unsigned int error:1; // True if any error is set
unsigned int framing_error:1;// A framing error occurred
unsigned int parity_error:1; // Character had the wrong parity
unsigned int carrier_lost:1; // The carrier signal went down
unsigned int channel_down:1; // Power was lost on the channel
};
Using packed structures for flags is clearer and less
error-prone
than using bitwise operators. However, bitwise operators allow additional flexibility. You should use the approach that is clearest and
easiest
for you to use.
Question 12-1:
Why does Example 12-1 fail?
Example 12-1. info/
info
.cpp
#include <iostream>
struct info {
int valid:1; // If 1, we are valid
int data:31; // The data
};
info a_info;
int main( )
{
a_info.valid = 1;
if (a_info.valid == 1)
std::cout << "a_info is valid\n";
return (0);
}
The GNU compiler does try to give a hint as to what's going on. Now if we could only understand what it is trying to say:
info.cpp: In function `int main( )':
info.cpp:13: warning: comparison is always 0 due to width of bitfield
|