Bitwise Operators in C
An overview of bitwise operators and their use in embedded software development
Four commonly used "bitwise operators" in C are: "~", "&", "|", and "^". These operators are used to manipulate the value of individual bits (1 or 0). There are also two 'shift' operators that it's important to understand: "<<" and ">>". The latter are used to 'shift' the position of a bit (or a set of bits) to another location in a multi-bit value. Since you will encounter them in almost any C code that you come across it's important to have a basic understanding of what they do, and when to use them.
~ (Bitwise Negation)
The negation operator ("~") is the easiest to understand. It will invert it's operand (the value it's used with), so that if we have a variable with a hexadecimal value of 0xF0F0 (1111000011110000), ~0xF0F0 will invert all of the bits in our value to give us 0x0F0F (0000111100001111).
Examples
| Initial Values | Resulting Value |
| a | b | ~a | ~b |
| 0 | 0 | 1 | 1 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 0 | 0 |
& (AND Operator)
The AND ("&") operator will return a '1-bit' if both bits are equal to '1', otherwise it will return 0.
Examples
| Initial Values | Resulting Value |
| a | b | a&b |
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
| (OR Operator)
The OR ("|") operator will return a '1-bit' if either bit is equal to '1', otherwise it will return 0.
Examples
| Initial Values | Resulting Value |
| a | b | a|b |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
^ (XOR Operator)
The XOR ("^") operator (aka "Exclusive OR") will return a '1-bit' if one bit is '0' and the other bit is '1' (i.e.: both operands are different), otherwise it will return 0.
Examples
| Initial Values | Resulting Value |
| a | b | a^b |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Shifting Bits
When working with collections of bits (such as hexadecimal numbers), there are two common bit shift operators that you will frequently encounter: "<<" (Left Shift) and ">>" (Right Shift). As their names imply, these operators will 'shift' the bit value a fixed number of places to the right or to the left.
Example: Left Shifting Bits
unsigned int i = 0xF0F0; // 1111 0000 1111 0000
unsigned int left = 0xF0F0 | (1 << 10); // 1111 0100 1111 0000
In the above example, we have taken a '1' bit, shifted it 10 places to the left (placing it at the 11th bit from the end, since we start at bit 1!), and 'inserted' it into a pre-existing value with the "|" OR operator discussed above.
Shifting bits like this and 'inserting' them with the "|" OR operator is an exceptionally common task when working with embedded software (since you often need to manipulate individual bits to turn features on or off or to configure your peripherals). As such, it's very important that you understand what these different operators means and how they function!
If you need to, you can also shift a set of values using hexadecimal, as follows:
unsigned int orig = 0xF0F0; // 1111 0000 1111 0000
unsigned int insert = 0x000A; // 0000 0000 0000 1010
unsigned int a = orig | (insert << 8); // 1111 1010 1111 0000
unsigned int b = orig | (insert << 6); // 1111 0010 1111 0000
You may have noticed in the last line of the example above that when we shifted the value only 6 places to the left, we ended up with 1111 0010 1111 0000 and not 1111 0010 1011 0000. The reason for this is because we are using an OR operator to combine the two values. To see the results different operators would have in this case, take a look at the following code and try to figure out why we end up with the results we have:
unsigned int orig = 0xF0F0; // 1111 0000 1111 0000
unsigned int insert = 0x000A; // 0000 0000 0000 1010
unsigned int OR = orig | (insert << 6); // 1111 0010 1111 0000
unsigned int AND = orig & (insert << 6); // 0000 0000 1000 0000
unsigned int XOR = orig ^ (insert << 6); // 1111 0010 0111 0000
The left shift operator isn't only useful for setting individual bits in a 32-bit value; it can also be used to 'read' a single bit from a larger value, as you can see in the following code:
unsigned long testValue = 0xFF00FF00;
// Check if the fifth bit is equal to 1 (hint: it isn't!)
if (testValue & (1 << 4))
{
// Fifth bit is equal to 1
}
else
{
// Fifth bit is equal to 0
}
Can you understand how this example code works? We are shifting a '1' bit 4 positions to the left (which will give us the following 32-bit value: 0000 0000 0000 0000 0000 0000 0001 0000). We are then 'comparing' this to our testValue with an & AND operator, which means that if bit 5 of our testValue is also 1 (as in the value we have just created), a '1' will be returned. If the values are different, the AND operator will fail and return 0.