Talk about C language bit field / bit segment

Posted by drummerboy on Sat, 05 Mar 2022 12:21:25 +0100

catalogue

1. Concepts and definitions

2. Examples

When doing embedded development, we often encounter such code:

struct
{
  unsigned int widthValidated : 1;
  unsigned int heightValidated : 1;
} status;

What does it mean to define structure variables in this way?

The main reason is that when some information is stored, it only needs to occupy a few or one binary bit, and it does not need to occupy a complete byte. For example, when storing a switching value, there are only two states: 0 and 1, and one binary can be used. In order to save storage space and make processing simple, C language provides a data structure called "bit field" or "bit segment".

1. Concepts and definitions

Bit field: the binary in a byte is divided into several different areas, and the number of bits in each area is described. Each domain has a domain name, which allows you to operate by domain name in the program. In this way, several different objects can be represented by one byte binary field.

The bit field definition is similar to the structure definition in the form of:

struct Bit field structure name 
{

 Bit field list

};

The form of bit field list is:

type [member_name] : width ;

The following is a description of the variable elements in the bit field:

The use of bit field is the same as that of structure member. Its general form is:

Field variable name.Bit domain name
 Bit field variable name->Bit domain name

The biggest function of bit field is to save storage space. In essence, it is a structure type, but its members are allocated according to binary. For example, the following cases:

#include <stdio.h>
#include <string.h>
 
/* Define simple structures */
struct
{
  unsigned int widthValidated;
  unsigned int heightValidated;
} status1;
 
/* Define bit field structure */
struct
{
  unsigned int widthValidated : 1;
  unsigned int heightValidated : 1;
} status2;
 
int main( )
{
   printf( "Memory size occupied by status1 : %d\n", sizeof(status1));
   printf( "Memory size occupied by status2 : %d\n", sizeof(status2));
 
   return 0;
}

When the code is compiled and executed, it will produce the following results:

Memory size occupied by status1 : 8
Memory size occupied by status2 : 4

Structure status1 is composed of two normal unsigned int type variables, occupying 8 bytes of memory. Structure status2 is also an unsigned int type variable, but it only uses the first 2 bits of an unsigned int type memory. In fact, there are 30 bits unused, so the occupied memory is 4 bytes.

For the definition of bit field, there are the following explanations:

  • A bit field is stored in the same byte. If there is not enough space left in a byte to store another bit field, the bit field will be stored from the next unit. You can also intentionally start a bit field from the next cell. For example:
struct bs{
    unsigned a:4;
    unsigned  :4;    /* airspace */
    unsigned b:4;    /* Store from the next unit */
    unsigned c:4
}

In this bit field definition, a occupies 4 bits of the first byte, the last 4 bits are filled with 0 to indicate not to use, b starts from the second byte, occupies 4 bits, and c occupies 4 bits.

  • The width of a bit field cannot exceed the length of the data type it is attached to. All member variables have types. This type limits the maximum length of member variables. The following number cannot exceed this length.

  • The bit field can be an anonymous bit field, which is only used for filling or adjusting the position. Nameless bit fields cannot be used. For example:

struct k{
    int a:1;
    int  :2;    /* The 2 bits cannot be used */
    int b:3;
    int c:2;
};
  • When the types of adjacent members are the same, if the sum of their bit widths is less than the sizeof size of the type, the subsequent members are stored next to the previous member until they cannot be accommodated; If the sum of their bit widths is greater than the sizeof size of the type, the subsequent members will start from the new storage unit with an offset of an integer multiple of the size of the type. For example:
#include <stdio.h>

int main(){
    struct bs{
        unsigned m: 6;
        unsigned n: 12;
        unsigned p: 4;
    };

    printf("%d\n", sizeof(struct bs));

    return 0;
}

Operation results:

4

m. The types of n and p are unsigned int, and the result of sizeof is 4 bytes, that is, 32 bits. m. The sum of the Bit widths of n and p is 6 + 12 + 4 = 22, less than 32, so they will be stored next to each other with no gap in the middle.

  • When the types of adjacent members are different, different compilers have different implementation schemes. GCC will compress the storage, but VC/VS will not. For example:
#include <stdio.h>

int main(){
    struct bs{
        unsigned m: 12;
        unsigned char ch: 4;
        unsigned p: 4;
    };

    printf("%d\n", sizeof(struct bs));

    return 0;
}

The running result under GCC is 4, and three members are stored next to each other; The running result under VC/VS is 12, and the three members are stored according to their respective types (the same as the storage method when the positioning width is not specified).

  • If non bit domain members are interspersed between members, compression will not be performed. For example:
struct bs{
    unsigned m: 12;
    unsigned ch;
    unsigned p: 4;
};

sizeof results in 12 under each compiler.

Note: bit domain members often do not occupy complete bytes, and sometimes they are not at the beginning of bytes. Therefore, it is meaningless to use & to obtain the address of bit domain members, which is also prohibited in C language. The address is the number of bytes, not bits.  

2. Examples

Deepen the understanding and application of bit domain through an example:

#include <stdio.h>
#include <string.h>
 
struct
{
  unsigned int age : 3;
} Age;
 
int main( )
{
   Age.age = 4;
   printf( "Sizeof( Age ) : %d\n", sizeof(Age) );
   printf( "Age.age : %d\n", Age.age );
 
   Age.age = 7;
   printf( "Age.age : %d\n", Age.age );
 
   Age.age = 8; // Binary representation of 1000 has four bits, exceeding
   printf( "Age.age : %d\n", Age.age );
 
   return 0;
}

When the above code is compiled, it will have a warning. When the above code is executed, it will produce the following results:

Sizeof( Age ) : 4
Age.age : 4
Age.age : 7
Age.age : 0

When executing to age age = 8; The binary representation of 1000 has four bits, which exceeds the bit field, so a warning will be prompted.

Topics: C data structure Programmer Embedded system