Bit Fields

From RAD Studio
Jump to: navigation, search

Go Up to Structures Index

Bit fields are specified numbers of bits that may or may not have an associated identifier. Bit fields offer a way of subdividing structures (structs, unions, classes) into named parts of user-defined sizes.

Declaring bit fields

You specify the bit-field width and optional identifier as follows:

type-specifier <bitfield-id> : width;

In C++, type-specifier is bool, char, unsigned char, short, unsigned short, long, unsigned long, int, unsigned int, __int64 or unsigned __int64. In strict ANSI C, type-specifier is int or unsigned int.

The expression width must be present and must evaluate to a constant integer. In C++, the width of a bit field may be declared of any size. In strict ANSI C, the width of a bit field may be declared only up to the size of the declared type. A zero length bit field skips to the next allocation unit.

If the bit field identifier is omitted, the number of bits specified in width is allocated, but the field is not accessible. This lets you match bit patterns in, say, hardware registers where some bits are unused.

Bit fields can be declared only in structures, unions, and classes. They are accessed with the same member selectors ( . and ->) used for non-bit-field members.

Limitations of using bit fields

When using bit fields, be aware of the following issues:

  • The code will be non-portable since the organization of bits-within-bytes and bytes-within-words is machine dependent.
  • You cannot take the address of a bit field; so the expression &mystruct.x is illegal if x is a bit field identifier, because there is no guarantee that mystruct.x lies at a byte address.
  • Bit fields are used to pack more variables into a smaller data space, but causes the compiler to generate additional code to manipulate these variables. This costs in terms of code size and execution time.

Because of these disadvantages, using bit fields is generally discouraged, except for certain low-level programming. A recommended alternative to having one-bit variables, or flags, is to use defines. For example:

#define Nothing 0x00
#define bitOne 0x01
#define bitTwo 0x02
#define bitThree 0x04
#define bitFour 0x08
#define bitFive 0x10
#define bitSix 0x20
#define bitSeven 0x40
#define bitEight 0x80

can be used to write code like:

if (flags & bitOne) {...}    // is bit One turned on
flags |= bitTwo;               // turn bit Two on
flags &= ~bitThree;         // turn bit Three off

Similar schemes can be made for bit fields of any size.

Padding of bit fields

In C++, if the width size is larger than the type of the bit field, the compiler will insert padding equal to the requested width size minus the size of the type of the bit field. So, declaration:

struct mystruct
{
  int i : 40;
  int j : 8;
};

will create a 32 bit storage for 'i', an 8 bit padding, and 8 bit storage for 'j'. To optimize access, the compiler will consider 'i' to be a regular int variable, not a bit field.

Layout and alignment

Bit fields are broken up into groups of consecutive bit fields of the same type, without regard to signedness. Each group of bit fields is aligned to the current alignment of the type of the members of the group. This alignment is determined by the type AND by the setting of the overall alignment (set by the byte alignment option -aN). Within each group, the compiler will pack the bit fields inside of areas as large as the type size of the bit fields. However, no bit field is allowed to straddle the boundary between 2 of those areas. The size of the total structure will be aligned, as determined by the current alignment.

Example of bit field layout, padding, and alignment

In the following C++ declaration, my_struct contains 6 bit fields of 3 different types, int, long, and char:

struct my_struct
{
   int one : 8;
   unsigned int two : 16;
   unsigned long three : 8;
   long four : 16;
   long five : 16;
   char six : 4;
};

Bit fields 'one' and 'two' will be packed into one 32-bit area.

Next, the compiler inserts padding, if necessary, based on the current alignment, and the type of three, because the type changes between the declarations of variables two and three. For example, if the current alignment is byte alignment (-a1), no padding is needed; whereas, if the alignment is 4 bytes (-a4), then 8-bit padding is inserted.

Next, variables three, four, and five are all of type long. Variables three and four are packed into one 32 bit area, but five can not be packed into that same area, since that would create an area of 40 bits, which is more than the 32 bit allowed for the long type. To start a new area for five, the compiler would insert no padding if the current alignment is byte alignment, or would insert 8 bits of padding if the current alignment is dword (4 byte) alignment.

With variable six, the type changes again. Since char is always byte aligned, no padding is needed. To force alignment for the whole struct, the compiler will finish up the last area with 4 bits of padding if byte alignment is used, or 12 bits of padding if dword alignment is used.

The total size of my_struct is 9 bytes with byte alignment, or 12 bytes with dword alignment.

To get the best results when using bit fields you should:

  • Sort bit fields by type
  • Make sure they are packed inside the areas by ordering them such that no bit field will straddle an area boundary
  • Make sure the struct is filled as much as possible.

Another recommendation is to force byte alignment for this struct, by emitting "#pragma option -a1". If you want to know how big your struct is, follow it by "#pragma sizeof(mystruct)", which gives you the size.

Using one bit signed fields

For a signed type of one bit, the possible values are 0 or -1. For an unsigned type of one bit, the possible values are 0 or 1. Note that if you assign "1" to a signed bit field, the value will be evaluated as -1 (negative one).

When storing the values true and false into a one bit sized bit field of a signed type, you can not test for equality to true because signed one bit sized bit fields can only hold the values '0' and '-1', which are not compatible with true and false. You can, however, test for non-zero.

For unsigned varieties of all types, and of course for the bool type, testing for equality to true will work as expected.

Thus:

struct mystruct
{
   int flag : 1;
} M;
int testing()
{
   M.flag = true;
   if(M.flag == true)
     printf("success");}

will NOT work, but:

struct mystruct
{
   int flag : 1;
} M;
int testing()
{
   M.flag = true;
   if(M.flag)
     printf("success");
}

works just fine.

Notes on compatibility

Between versions of the compiler, changes can be made to default alignment, or for purposes of compatibility with other compilers. Consequently, this could change the alignment of bit fields. Therefore, there is no guarantee that the alignment of bit fields will be consistent between versions of the compiler. To check the backward compatibility of bit fields in your code you can add an assert statement that checks for the structure size you are expecting.

According to the C and C++ language specifications, the alignment and storage of bit fields is implementation defined. Therefore, compilers can align and store bit fields differently. If you want complete control over the layout of bit fields, it is advisable to write your own bit field accessing routines and create your own bit fields.