The same programming kung fu used to read a single bit also applies to wide bit fields. The process involves masking and shifting: You must know the bit field’s width and position in an integer value. Once those values are obtained — no matter how wide the bit field — a single function handles the job.
The function I crafted to read a wide bit field is called bit_field_read(). Here is its prototype:
unsigned char bit_field_read(char bit, char width, char byte);
The argument bit
is the position in the integer value, here limited to unsigned char variables. Position 0 is the far right bit and the values range up to 7 for the far left bit.
width
is the size of the chunk to read, which can range from 1 to read a single bit or 8 to read the entire byte.
Finally, byte
is the value containing the bit field.
My function doesn’t do any error checking, so it’s possible to read a bit not within the byte or a chunk wider than the byte. If I were coding bit_field_read() for use “in the wild” I would definitely add such bounds checking.
What the bit_field_read() function does is to shift bits in the byte right so that the field starts at bit 0. Then the field width is masked. The result is the value desired. Figure 1 demonstrates how a bit field 4-bits wide at position 2 would be processed by the bit_field_read() function.
The tough part of the function is to convert the width
value into a proper bit mask. For example, the value 2 specifies a width of two bits, but in binary the value 2 is 10
and the mask should be 11
, which is decimal 3. Table 1 shows how the width values need to be converted.
Field width | Binary mask | Decimal value (unsigned) |
---|---|---|
1 | 0000-0001 | 1 |
2 | 0000-0011 | 3 |
3 | 0000-0111 | 7 |
4 | 0000-1111 | 15 |
5 | 0001-1111 | 31 |
6 | 0011-1111 | 63 |
7 | 0111-1111 | 127 |
8 | 1111-1111 | 255 |
To solve the puzzle, you must get from a value like 3 to the value 7. The solution I devised uses binary manipulation to add a bit to a byte, shift the value left, then keep looping to add bits. Here is that code snippet:
mask = 0; while(width--) { mask = mask << 1; mask |= 1; }
The variable width
holds the original width value, such as 3. mask
holds the final value, such as 7. The mask starts as 0. Its bits are shifted right, which has no effect when the loop begins. Then the mask |= 1
statement sets bit 0. If the value of width
is larger, the loop continues. Bit 0 is shifted to bit 1, then bit 0 is set again. Figure 2 illustrates this process.
In the end, the width
value is converted the proper mask
value, just as shown in Table 1. The mask
value is used in the bit_field_read() function to properly mask bits in the given integer value.
Here is a demo program that does the same conversions as shown in last week’s Lesson, though the bit_field_read() function is used instead:
#include <stdio.h> char bit_field_read(char bit, char width, char byte); int main() { unsigned char board; board = 23; printf("The Queen is at row %d, column %d\n", bit_field_read(3,3,board)+1, bit_field_read(0,3,board)+1 ); return(0); } char bit_field_read(char bit, char width, char byte) { unsigned char mask; mask = 0; while(width--) { mask = mask << 1; mask |= 1; } byte = byte >> bit; return( byte & mask ); }
The output from the code is the same as last week:
The Queen is at row 3, column 8
The bit_field_read() function works on any size field from 1 bit wide to the entire byte. So the function can also be used to read on-off values similar to the bit_test() function from an earlier Lesson.
A bit_field_write() function involves a bit more manipulation to put a bit field into an integer value. I’ll demonstrate that function in next week’s Lesson.