Is Bit i Set?

Introduction

So you're wondering, what can we do with bitwise and bitshift operators. One motivation behind learning these operators is to access or manipulate bits. Why would you want to do this?

You might want to represent 32 boolean variables very compactly. You can use an int variable (assuming it has 4 bytes) as a 32 bit Boolean array. Unlike the bool type in C++, which presumably requires one byte, you can make your own Boolean variable using a single bit. However, to do so, you need to access individual bits.

Another reason. Sometimes you are reading input from a device. Each bit may indicate a status for the device or it may be one bit of control for that device.

Bit manipulation is considered really low-level programming. There are some who believe that being able to access the individual bits is bad abstraction. Thus, many higher level languages hide the representation from the programmer. However, languages like C are often used for systems programming where data representation is quite important.

Wishing Doesn't Always Make it So

Suppose you had a character variable, declared like:
  char ch ;
How might you set the least significant bit to 1? You might be tempted to do the following:
  ch[ 0 ] = 1 ; // WRONG!  Does not set bit 0 of ch
After all, aren't arrays wonderful? Why shouldn't this work? Why shouldn't C allow us this nice and simple way to manipulate bits? Alas, it doesn't.

The reason has to do with how arrays are defined in C.

arr[ i ] is really defined as *( arr + i ) where arr is & arr[ 0 ] and + i does pointer arithmetic. The compiler performs the following computatation.

  address of arr[ i ] is
    addr( arr ) + ( i * sizeof(type of array element) )
Thus, you find out what address arr is at, then multiply i by the number of bytes for the type of one element in the array. This creates an address. Recall that addresses in memory refer to bytes, not to individual bits. That's where the problem lies. The semantics of array access refer to memory addresses of bytes.

So, we need to find other ways to access individual bits. As you might guess, one way to do this is to use bitshift and bitwise operators.

Mask

For our examples, we'll work with 8-bit bitstrings. I could look at 32 bit ints, but they're 32 bits, and that's a lot of bits to write. The principle still apply.

We're going to consider bitstring pattern called a mask. Here's one example of a mask.

b7 b6 b5 b4 b3 b2 b1 b0
0 0 0 0 1 0 0 0

This is a bitstring with exactly one bit that has a value of 1. In this example, b3 = 0.

Similarly, the bitflipped version of this is also considered a mask.

b7 b6 b5 b4 b3 b2 b1 b0
1 1 1 1 0 1 1 1

What's the purpose of a mask? The purpose is to either access an individual bit (or range of bits) or to modify an individual bit (or range of bits).

Creating a Mask

Before we see how to use a mask, let's write code to create a mask. This turns out to be rather simple.
   unsigned char mask = static_cast( 1 << i ) ;
What does 1 << i do? The problem is what type is 1? It's most likely an int, which means that it's mostly like 31 zeroes followed by a 1. So, 1 << i causes bi = 1 while the rest of the bits are 0.

This creates the first kind of mask shown above.

To create the second kind, you can do bitwise negation on the mask from the first part, as in:

   unsigned char mask = static_cast( 1 << i ) ;
   mask = ~mask ;  // flip the bits to create an inverted mask
You might wonder if it's strictly necessary to do the static cast (at least, in C++)? The answer depends. If the compiler doesn't complain, then you don't need to do it.

Using the Mask

Let's see how we can use this mask to determine whether a bit is set or clear. Before we can do that, perhaps we should define what it means to be set or clear.

Suppose you're asked to write a function that returns true if bit i is set, and false if it's clear. We'll call this function isBitISet().

Here's the prototype:

bool isBitISet( unsigned char ch, int i ) ;
Here's the code.
bool isBitISet( unsigned char ch, int i ) 
{
   unsigned char mask = static_cast( 1 << i ) ;
   return mask & ch ;   
}
It looks pretty simple, doesn't it? Create a mask, then use bitwise AND on the character.

Here's why it works.

ch b7 b6 b5 b4 b3 b2 b1 b0
mask 0 0 0 0 1 0 0 0
result = ch & mask 0 0 0 0 b3 0 0 0

We don't particularly care what the character looks like. Just assume it's some 8-bit bitstring, b7...b0. To distinguish the character from the mask, let's label the bits of the mask as m7...m0.

The mask has bit m3 set, though 3 was picked arbitrarily. Notice what happens when you bitwise AND the mask and the character.

The bits which are 0 in the mask cause the result to also be 0 (since 0 & bi = 0). Bit b3 is ANDed with the '1' appearing in the mask at position m3. That results in b3.

If b3 = 1, then result is non-zero. If b3 = 0, then result is zero.

The only interesting part is the return type. The result of the AND operation is an unsigned char. We're returning a bool (in C, you'd return an int or a short).

In C++, converting between char, int, and bool rarely requires a cast. Basically, anything that evaluates to 0 for a given type, is false when converted to a bool Anything that evaluates to a non-zero value (which doesn't have to be 1), evaluates to true.

Summary

Using a mask, we can determine whether bit i of a char or an int is set or not. We use bitwise/bitshift operators to accomplish this task.

Web Accessibility