Tuesday 8 November 2011

What is meant by “bit masking”? in C programming

What is meant by “bit masking”?

Bit masking means selecting only certain bits from byte(s) that might have many bits set. To examine some
bits of a byte, the byte is bitwise “ANDed” with a mask that is a number consisting of only those bits of interest.

For instance, to look at the one’s digit (rightmost digit) of the variable flags, you bitwise AND it with a mask
of one (the bitwise AND operator in C is &):
flags & 1;
To set the bits of interest, the number is bitwise “ORed” with the bit mask (the bitwise OR operator in C is
|). For instance, you could set the one’s digit of flags like so:
flags = flags | 1;
Or, equivalently, you could set it like this:
flags |= 1;
To clear the bits of interest, the number is bitwise ANDed with the one’s complement of the bit mask. The
“one’s complement” of a number is the number with all its one bits changed to zeros and all its zero bits
changed to ones. The one’s complement operator in C is ~. For instance, you could clear the one’s digit of
flags like so:
flags = flags & ~1;
Or, equivalently, you could clear it like this:
flags &= ~1;

Sometimes it is easier to use macros to manipulate flag values. Listing X.2 shows a program that uses some
macros to simplify bit manipulation.
/* Bit Masking */
/* Bit masking can be used to switch a character
between lowercase and uppercase */
#define BIT_POS(N) ( 1U << (N) )
#define SET_FLAG(N, F) ( (N) |= (F) )
#define CLR_FLAG(N, F) ( (N) &= -(F) )
#define TST_FLAG(N, F) ( (N) & (F) )
#define BIT_RANGE(N, M) ( BIT_POS((M)+1 - (N))-1 << (N) )
#define BIT_SHIFTL(B, N) ( (unsigned)(B) << (N) )
#define BIT_SHIFTR(B, N) ( (unsigned)(B) >> (N) )
#define SET_MFLAG(N, F, V) ( CLR_FLAG(N, F), SET_FLAG(N, V) )
#define CLR_MFLAG(N, F) ( (N) &= ~(F) )
#define GET_MFLAG(N, F) ( (N) & (F) )
#include <stdio.h>
void main()
{
unsigned char ascii_char = ‘A’; /* char = 8 bits only */
int test_nbr = 10;
printf(“Starting character = %c\n”, ascii_char);
/* The 5th bit position determines if the character is
uppercase or lowercase.
5th bit = 0 - Uppercase
5th bit = 1 - Lowercase */
printf(“\nTurn 5th bit on = %c\n”, SET_FLAG(ascii_char, BIT_POS(5)) );
printf(“Turn 5th bit off = %c\n\n”, CLR_FLAG(ascii_char, BIT_POS(5)) );
printf(“Look at shifting bits\n”);
printf(“=====================\n”);
printf(“Current value = %d\n”, test_nbr);
printf(“Shifting one position left = %d\n”,
test_nbr = BIT_SHIFTL(test_nbr, 1) );
printf(“Shifting two positions right = %d\n”,
BIT_SHIFTR(test_nbr, 2) );
}

BIT_POS(N) takes an integer N and returns a bit mask corresponding to that single bit position (BIT_POS(0)
returns a bit mask for the one’s digit, BIT_POS(1) returns a bit mask for the two’s digit, and so on). So instead of writing

#define A_FLAG 4096
#define B_FLAG 8192
you can write
#define A_FLAG BIT_POS(12)
#define B_FLAG BIT_POS(13)
which is less prone to errors.
The SET_FLAG(N, F) macro sets the bit at position F of variable N. Its opposite is CLR_FLAG(N, F), which clears
the bit at position F of variable N. Finally, TST_FLAG(N, F) can be used to test the value of the bit at position
F of variable N, as in
if (TST_FLAG(flags, A_FLAG))
/* do something */;
The macro BIT_RANGE(N, M) produces a bit mask corresponding to bit positions N through M, inclusive. With
this macro, instead of writing
#define FIRST_OCTAL_DIGIT 7 /* 111 */
#define SECOND_OCTAL_DIGIT 56 /* 111000 */
you can write
#define FIRST_OCTAL_DIGIT BIT_RANGE(0, 2) /* 111 */
#define SECOND_OCTAL_DIGIT BIT_RANGE(3, 5) /* 111000 */
which more clearly indicates which bits are meant.
The macro BIT_SHIFT(B, N) can be used to shift value B into the proper bit range (starting with bit N). For
instance, if you had a flag called C that could take on one of five possible colors, the colors might be defined
like this:

#define C_FLAG BIT_RANGE(8, 10) /* 11100000000 */
/* here are all the values the C flag can take on */
#define C_BLACK BIT_SHIFTL(0, 8) /* 00000000000 */
#define C_RED BIT_SHIFTL(1, 8) /* 00100000000 */
#define C_GREEN BIT_SHIFTL(2, 8) /* 01000000000 */
#define C_BLUE BIT_SHIFTL(3, 8) /* 01100000000 */
#define C_WHITE BIT_SHIFTL(4, 8) /* 10000000000 */
#define C_ZERO C_BLACK
#define C_LARGEST C_WHITE
/* A truly paranoid programmer might do this */
#if C_LARGEST > C_FLAG
Cause an error message. The flag C_FLAG is not
big enough to hold all its possible values.
#endif /* C_LARGEST > C_FLAG */
The macro SET_MFLAG(N, F, V) sets flag F in variable N to the value V. The macro CLR_MFLAG(N, F) is identical

to CLR_FLAG(N, F), except the name is changed so that all the operations on multibit flags have a similar
naming convention. The macro GET_MFLAG(N, F) gets the value of flag F in variable N, so it can be tested,
as in

if (GET_MFLAG(flags, C_FLAG) == C_BLUE)
/* do something */;

NOTE
Beware that the macros BIT_RANGE() and SET_MFLAG() refer to the N argument twice, so
the expression

SET_MFLAG(*x++, C_FLAG, C_RED);
will have undefined, potentially disastrous behavior.

Cross Reference:

X.1: What is the most efficient way to store flag values?
X.3: Are bit fields portable?

No comments:

Post a Comment