Bit Fields
If the structure of a program contains multiple switch variables, each being a TRUE/FALSE variable, like this:
struct
{
unsigned int widthValidated;
unsigned int heightValidated;
} status;
This structure requires 8 bytes of memory space, but in reality, we only store 0 or 1 in each variable. In such cases, C provides a better way to utilize memory space. By defining the width of variables within the structure, you can instruct the compiler to use only these bytes. For example, the above structure can be rewritten as:
struct
{
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status;
Now, the status variable in the above structure will occupy 4 bytes of memory space, but only 2 bits are used to store values. If you use 32 variables, each with a width of 1 bit, the status structure will use 4 bytes. However, if you add one more variable, making it 33 variables, it will allocate the next segment of memory to store the 33rd variable, thus using 8 bytes. Let's look at the following example to understand this concept:
Example
#include <stdio.h>
#include <string.h>
/* Define simple structure */
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 above code is compiled and executed, it produces the following result:
Memory size occupied by status1 : 8
Memory size occupied by status2 : 4
Bit Field Declaration
Some information does not need to occupy a full byte and only requires a few or one bit. For example, storing a switch variable with only 0 and 1 states can be done with 1 bit. To save storage space and simplify processing, C provides a data structure called "bit field" or "bit segment".
A "bit field" divides a byte into different areas and specifies the number of bits for each area. Each field has a name, allowing operations by field name. This way, several different objects can be represented using a byte's bit fields.
Typical examples:
- Storing a switch variable with 1 bit, having only 0 and 1 states.
- Reading external file formats—can read non-standard file formats. For example: 9-bit integers.
Bit Field Definition and Bit Field Variable Declaration
Bit field definition is similar to structure definition, in the form:
struct bit_field_structure_name
{
bit_field_list
};
Where the bit field list is in the form:
type [member_name] : width ;
Here is a description of the bit field elements:
Element | Description |
---|---|
type | Can only be int, unsigned int, or signed int, determining how the bit field value is interpreted. |
member_name | The name of the bit field. |
width | The number of bits in the bit field. The width must be less than or equal to the bit width of the specified type. |
Variables with predefined widths are called bit fields. A bit field can store more than 1 bit. For example, to store values from 0 to 7, you can define a bit field with a width of 3 bits, like this:
struct
{
unsigned int age : 3;
} Age;
The above structure instructs the C compiler that the age variable will only use 3 bits to store the value. If you try to use more than 3 bits, it will not be possible.
struct bs{
int a:8;
int b:2;
int c:6;
}data;
data is a bs variable, occupying two bytes in total. The bit field a occupies 8 bits, bit field b occupies 2 bits, and bit field c occupies 6 bits.
Let's look at another example:
struct packed_struct {
unsigned int f1:1;
unsigned int f2:1;
unsigned int f3:1;
unsigned int f4:1;
unsigned int type:4;
unsigned int my_int:9;
} pack;
Here, packed_struct contains 6 members: four 1-bit identifiers f1..f4, a 4-bit type, and a 9-bit my_int.
Let's look at the following example:
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 is 1000, which has four bits, exceeding the limit
printf( "Age.age : %d\n", Age.age );
return 0;
}
When the above code is compiled, it comes with a warning. When executed, it produces the following result:
Sizeof( Age ) : 4
Age.age : 4
Age.age : 7
Age.age : 0
Notes on Bit Field Definition:
- A bit field is stored in the same byte. If the remaining space in a byte is not enough to store another bit field, it will start storing from the next byte. You can also intentionally start a bit field from the next byte. For example:
struct bs{ unsigned a:4; unsigned :4; /* Empty field */ unsigned b:4; /* Starts storing from the next byte */ unsigned c:4 }
In this bit field definition, a occupies the first byte's 4 bits, the next 4 bits are filled with 0 and not used, b starts from the second byte, occupying 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. The variable has a type, which limits the maximum length of the variable. The number after
:
cannot exceed this length.A bit field can be unnamed, in which case it is only used for padding or adjusting positions. Unnamed bit fields cannot be used. For example:
struct k{ int a:1; int :2; /* These 2 bits cannot be used */ int b:3; int c:2; };
From the above analysis, it can be seen that a bit field is essentially a structure type, but its members are allocated in bits.
Bit Field Usage
Bit fields are used similarly to structure members, with the general form:
bit_field_variable_name.bit_field_name
bit_field_variable_name->bit_field_name
Bit fields can be output in various formats.
Please look at the following example:
Example
#include <stdio.h>
int main(){
struct bs{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1; /* Assign value to bit field (should not exceed the allowed range) */
bit.b=7; /* Assign value to bit field (should not exceed the allowed range) */
bit.c=15; /* Assign value to bit field (should not exceed the allowed range) */
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* Output the contents of the three fields in integer format */
pbit=&bit; /* Assign the address of bit field variable bit to pointer variable pbit */
pbit->a=0; /* Reassign value to bit field a using pointer, set to 0 */
pbit->b&=3; /* Use compound bitwise operator "&=", equivalent to: pbit->b=pbit->b&3, the original value of bit field b is 7, the result of bitwise AND with 3 is 3 (111&011=011, decimal value is 3) */
pbit->c|=1; /* Use compound bitwise operator "|=", equivalent to: pbit->c=pbit->c|1, the result is 15 */
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* Output the values of these three fields using pointer */
}
The above program defines a bit field structure bs with three bit fields a, b, and c. It declares a bs type variable bit and a pointer variable pbit pointing to bs type. This indicates that bit fields can also use pointers.