С++. Bit fields in structures.

Bit fields in structures. Examples


Contents


Search other resources:

1. Bit fields. General concepts

Structures allow you to conveniently group data (variables) according to some criteria. In memory, such data, as a rule, are placed one after another according to the declaration. In the C++ language there is a means of compressing the size of the memory allocated for the variables declared in the structure. These are bit fields in structures.

Bit fields are used to store data of integer types. When using bit fields in the size of the type, a strictly defined number of bits are allocated for them.

There are times when the size of the memory that is allocated for storing data is too large. For example, to store data about the day of the week, the size of 1 byte or 8 bits is too much. There are 7 days of the week, so 3 bits are enough to represent this data (2 to the power of 3 = 8 values).

The bitfield declaration in the structure looks like this

unsigned int field : value;

here

  • field – the name of the field (variable) in the structure;
  • value – the number of bits allocated to represent the set of bit field values.

In turn, the declaration of a structure containing bit fields looks like this:

struct StructName
{
  unsigned int field_1 : value_1;
  unsigned int field_2 : value_2;
  ...
  unsigned int field_N : value_N
};

here

  • StructName – the name of the structure;
  • field_1, field_2, field_N – variable names that correspond to the bit fields of the structure;
  • value_1, value_2, value_N – integer values representing the number of allocated bits for the sizes of fields field_1, field_2, field_N respectively.

It is recommended to use unsigned int type as bit fields. This will not distort the result if the first bit is set to 1, which will mean a negative number. However, when using other integer types, the compiler will not generate errors.

Declaring only one bitfield in the structure has no effect. Data in memory will still be stored in multiples of 8 bits or 1 byte (alignment). The usefulness of bit fields increases when a large amount of integer data is stored in the structure. With a large number of bit fields, you can save a significant amount of bytes for the same data set without losing them. In turn, the use of arrays of structures with bit fields can significantly reduce the size of stored data and speed up the execution of commands for processing this data.

 

2. An example of using bit fields in a Date structure describing the date of the day and the number of the day in the week

The task declares the Day structure that describes the following data about the day:

  • the number of the day in the week from 1 to 7. Here 1 is Monday, 2 is Tuesday, etc., 7 is Sunday;
  • day number in the month – 1..31;
  • month number – 1..12;
  • year. The range is from 0 to 3000.

To represent the day of the week, 3 bits are sufficient, since 3 bits yield 8 values (2^3=8). That is, 8 values cover 7 values that are required to store the weekday number. Based on the above reasoning about the day of the week, you can form the number of bits for other fields of the Day structure:

  • the number in the month is enough to represent 5 bits (2^5 = 32, 32>31);
  • it is enough to represent the month number with 4 bits (2^4 = 16, 16>12);
  • 12 bits are enough to store the year (2^12 = 4096, 4096>3000).

The demo program text is as follows

#include <iostream>
using namespace std;

struct Day
{
  unsigned int weekDay : 3; // day of week 1..7
  unsigned int number : 5;  // day number in month 1..31
  unsigned int month : 4;   // month number 1..12
  unsigned int year : 12;   // year 0..3000
};

void main()
{
  // Bit fields in structures
  // 1. Declare structure
  Day d;

  // 2. Fill the structure with data 01/14/2022, Friday
  d.weekDay = 5;
  d.number = 14;
  d.month = 1;
  d.year = 2022;

  // 3. Print data
  cout << "d.weekDay = " << d.weekDay << endl;
  cout << "d.number = " << d.number << endl;
  cout << "d.month = " << d.month << endl;
  cout << "d.year = " << d.year << endl;

  // 4. Display the size of structure
  cout << "sizeof(Day) = " << sizeof(d) << endl; // 4 bytes
}

Program result

d.weekDay = 5
d.number = 14
d.month = 1
d.year = 2022
sizeof(Day) = 4

 

3. An example of comparing a structure with bit fields to a structure that does not use bit fields. The Time structure

Let it be necessary to implement the Time structure, which describes the time interval in the form

hours : minutes : seconds : milliseconds

here

  • hours – number of hours in one day 0..23;
  • minutes – number of minutes per hour 0..59;
  • seconds – the number of seconds per minute 0..59;
  • milliseconds – number of milliseconds 0..999.

Bitfields are best used to describe such data in a structure. Based on the maximum allowable values, for different time intervals it is possible to allocate a different number of bits in their representation in structures:

  • the hours field – 5 bits (2^5 = 32>23). From 5 bits, you can form up to 2^5 = 32 different values, which is enough to represent 24 values of this field;
  • minutes – 6 bits (2^6 = 64 > 59);
  • seconds – 6 bits (2^6 = 64 > 59);
  • milliseconds – 10 bits (2^10 = 1024 > 999).

In the program, for the purpose of demonstration, two structures Time and Time2 are created, in which the necessary fields for representing time are implemented. However, the Time structure uses bitfields, and the Time2 structure uses ordinary unsigned int values.

The program text is as follows

#include <iostream>
using namespace std;

// The Time structure uses bit fields
struct Time
{
  unsigned int hours : 5;
  unsigned int minutes : 6;
  unsigned int seconds : 6;
  unsigned int milliseconds : 10;
};

// Time2 structure does not use fields
struct Time2
{
  unsigned int hours;
  unsigned int minutes;
  unsigned int seconds;
  unsigned int milliseconds;
};

void main()
{
  // Comparison of structures with and without bit fields
  // 1. Declare the variables
  Time tm;
  Time2 tm2;

  // 2. Using structures in the program
  // 2.1. The structure of Time type
  tm.hours = 12;
  tm.seconds = 36;
  tm.milliseconds = 777;
  tm.minutes = 3;

  // 2.2. The structure of Time2 type
  tm2.hours = 20;
  tm2.seconds = 39;
  tm2.milliseconds = 555;
  tm2.minutes = 55;

  // 3. Determine and display the size of the Time, Time2 structures
  //    using the sizeof() operator
  size_t sizeTime = sizeof(Time);     // 4
  size_t sizeTime2 = sizeof(Time2);   // 16
  cout << "sizeTime = " << sizeTime << endl;
  cout << "sizeTime2 = " << sizeTime2 << endl;
}

Program result

sizeTime = 4
sizeTime2 = 16

As you can see from the result, there is no difference between the use of ordinary structures and bit structures (access to fields, filling with data, etc.). However, the size of the bit structure (variable tm) in memory is only 4 bytes compared to the size of the ordinary structure of 16 bytes.

In the Time structure, bits are added to the fields. The total number of bits is

5 + 6 + 6 + 10 = 27

If you divide 27 by 8 (the number of bits in a byte), you get 3 with an excess. That is, to represent 27 bits, 4 bytes are needed to correctly display the data.

 


Related topics