Suppose your C program contains a number of TRUE/FALSE variables grouped in a structure called status, as follows:
The variables defined with a predefined width are called bit fields.
A bit field can hold more than a single bit for example if you need a
variable to store a value from 0 to 7 only then you can define a bit
field with a width of 3 bits as follows:
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 funny_int:9; } pack;
Here the packed_struct contains 6 members: Four 1 bit flags f1..f3, a 4 bit type and a 9 bit funny_int.
C automatically packs the above bit fields as compactly as possible, provided that the maximum length of the field is less than or equal to the integer word length of the computer. If this is not the case then some compilers may allow memory overlap for the fields whilst other would store the next field in the next word (see comments on bit fiels portability below).
Access members as usual via:
pack.type = 7;
NOTE:
Fig. Example Disk Controller Register We could define this register easily with bit fields:
struct DISK_REGISTER { unsigned ready:1; unsigned error_occured:1; unsigned disk_spinning:1; unsigned write_protect:1; unsigned head_loaded:1; unsigned error_code:8; unsigned track:9; unsigned sector:5; unsigned command:5; }; To access values stored at a particular memory address, DISK_REGISTER_MEMORY we can assign a pointer of the above structure to access the memory via:
struct DISK_REGISTER *disk_reg = (struct DISK_REGISTER *) DISK_REGISTER_MEMORY; The disk driver code to access this is now relatively straightforward:
/* Define sector and track to start read */ disk_reg->sector = new_sector; disk_reg->track = new_track; disk_reg->command = READ; /* wait until operation done, ready will be true */ while ( ! disk_reg->ready ) ; /* check for errors */ if (disk_reg->error_occured) { /* interrogate disk_reg->error_code for error type */ switch (disk_reg->error_code) ...... }
struct { unsigned int widthValidated; unsigned int heightValidated; } status;This structure requires 8 bytes of memory space but in actual we are going to store either 0 or 1 in each of the variables. The C programming language offers a better way to utilize the memory space in such situation. If you are using such variables inside a structure then you can define the width of a variable which tells the C compiler that you are going to use only those number of bytes. For example, above structure can be re-written as follows:
struct { unsigned int widthValidated : 1; unsigned int heightValidated : 1; } status;Now, the above structure will require 4 bytes of memory space for status variable but only 2 bits will be used to store the values. If you will use up to 32 variables each one with a width of 1 bit , then also status structure will use 4 bytes, but as soon as you will have 33 variables, then it will allocate next slot of the memory and it will start using 8 bytes. Let us check the following example to understand the concept:
#include <stdio.h> #include <string.h> /* define simple structure */ struct { unsigned int widthValidated; unsigned int heightValidated; } status1; /* define a structure with bit fields */ 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
The declaration of a bit-field has the form inside a structure:struct { type [member_name] : width ; };Below the description of variable elements of a bit field:
| Elements | Description |
|---|---|
| type | An integer type that determines how the bit-field's value is interpreted. The type may be int, signed int, unsigned int. |
| 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. |
struct { unsigned int age : 3; } Age;The above structure definition instructs C compiler that age variable is going to use only 3 bits to store the value, if you will try to use more than 3 bits then it will not allow you to do so. Let us try the following 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; printf( "Age.age : %d\n", Age.age ); return 0; }When the above code is compiled it will compile with warning and when executed, it produces the following result:
Sizeof( Age ) : 4 Age.age : 4 Age.age : 7 Age.age : 0
Bit Fields
Bit Fields allow the packing of data in a structure. This is especially useful when memory or data storage is at a premium. Typical examples:- Packing several objects into a machine word. e.g. 1 bit flags can be compacted -- Symbol tables in compilers.
- Reading external file formats -- non-standard file formats could be read in. E.g. 9 bit integers.
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 funny_int:9; } pack;
Here the packed_struct contains 6 members: Four 1 bit flags f1..f3, a 4 bit type and a 9 bit funny_int.
C automatically packs the above bit fields as compactly as possible, provided that the maximum length of the field is less than or equal to the integer word length of the computer. If this is not the case then some compilers may allow memory overlap for the fields whilst other would store the next field in the next word (see comments on bit fiels portability below).
Access members as usual via:
pack.type = 7;
NOTE:
- Only n lower bits will be assigned to an n bit number. So type cannot take values larger than 15 (4 bits long).
- Bit fields are always converted to integer type for computation.
- You are allowed to mix "normal" types with bit fields.
- The unsigned definition is important - ensures that no bits are used
as a
flag.
Bit Fields: Practical Example
Frequently device controllers (e.g. disk drives) and the operating system need to communicate at a low level. Device controllers contain several registers which may be packed together in one integer .Fig. Example Disk Controller Register We could define this register easily with bit fields:
struct DISK_REGISTER { unsigned ready:1; unsigned error_occured:1; unsigned disk_spinning:1; unsigned write_protect:1; unsigned head_loaded:1; unsigned error_code:8; unsigned track:9; unsigned sector:5; unsigned command:5; }; To access values stored at a particular memory address, DISK_REGISTER_MEMORY we can assign a pointer of the above structure to access the memory via:
struct DISK_REGISTER *disk_reg = (struct DISK_REGISTER *) DISK_REGISTER_MEMORY; The disk driver code to access this is now relatively straightforward:
/* Define sector and track to start read */ disk_reg->sector = new_sector; disk_reg->track = new_track; disk_reg->command = READ; /* wait until operation done, ready will be true */ while ( ! disk_reg->ready ) ; /* check for errors */ if (disk_reg->error_occured) { /* interrogate disk_reg->error_code for error type */ switch (disk_reg->error_code) ...... }
No comments:
Post a Comment