Preprocessor & Bitwise Opr
Preprocessor & Bitwise Opr
The C Preprocessor
The C preprocessor modifies a source code file before handing it over to the compiler. You're
most likely used to using the preprocessor to include files directly into other files, or #define
constants, but the preprocessor can also be used to create "inlined" code using macros
expanded at compile time and to prevent code from being compiled twice.
There are essentially three uses of the preprocessor--directives, constants, and macros.
Directives are commands that tell the preprocessor to skip part of a file, include another file,
or define a constant or macro. Directives always begin with a sharp sign (#) and for
readability should be placed flush to the left of the page. All other uses of the preprocessor
involve processing #define'd constants or macros. Typically, constants and macros are written
in ALL CAPS to indicate they are special.
Header Files
The #include directive tells the preprocessor to grab the text of a file and place it directly into
the current file. Typically, such statements are placed at the top of a program--hence the
name "header file" for files thus included.
Constants
If we write
#define [identifier name] [value]
whenever [identifier name] shows up in the file, it will be replaced by [value].
If you are defining a constant in terms of a mathematical expression, it is wise to surround the
entire value in parentheses:
#define PI_PLUS_ONE (3.14 + 1)
By doing so, you avoid the possibility that an order of operations issue will destroy the
meaning of your constant:
x = PI_PLUS_ONE * 5;
Without parentheses, the above would be converted to
x = 3.14 + 1 * 5;
which would result in 1 * 5 being evaluated before the addition, not after. Oops!
Conditional Compilation
2013 Page 1
Ankur Rastogi Unit-V C Pre Processor & Bitwise Operator
There are a whole set of options that can be used to determine whether the preprocessor will
remove lines of code before handing the file to the compiler. They include #if, #elif, #else,
#ifdef, and #ifndef. An #if or #if/#elif/#else block or a #ifdef or #ifndef block must be
terminated with a closing #endif.
The #if directive takes a numerical argument that evaluates to true if it's non-zero. If its
argument is false, then code until the closing #else, #elif, of #endif will be excluded.
Conditional compilation is a particularly useful way to comment out a block of code that
contains multi-line comments (which cannot be nested).
#if 0
/* comment ...
*/
// code
/* comment */
#endif
Example:
#define X 0
void main()
{
#if(X>0)
printf("positive");
#else
printf("negative");
#endif
getch();
}
Macros
The other major use of the preprocessor is to define macros. The advantage of a
macro is that it can be type-neutral (this can also be a disadvantage, of course), and
it's inlined directly into the code, so there isn't any function call overhead.
They look a lot like function calls, but they're not so simple. There are actually a couple of
tricky points when it comes to working with macros. First, remember that the exact text of the
macro argument is "pasted in" to the macro. For instance, if you wrote something like this:
#define MULT(x, y) x * y
2013 Page 2
Ankur Rastogi Unit-V C Pre Processor & Bitwise Operator
what value do you expect z to end up with? The obvious answer, 30, is wrong! That's because
what happens when the macro MULT expands is that it looks like this:
int z = 3 + 2 * 4 + 2; // 2 * 4 will be evaluated first!
So z would end up with the value 13! This is almost certainly not what you want to happen.
The way to avoid it is to force the arguments themselves to be evaluated before the rest of the
macro body. You can do this by surrounding them by parentheses in the macro definition:
#define MULT(x, y) (x) * (y)
// now MULT(3 + 2, 4 + 2) will expand to (3 + 2) * (4 + 2)
But this isn't the only gotcha! It is also generally a good idea to surround the macro's code in
parentheses if you expect it to return a value. Otherwise, you can get similar problems as
when you define a constant. For instance, the following macro, which adds 5 to a given
argument, has problems when embedded within a larger statement:
#define ADD_FIVE(a) (a) + 5
int x = ADD_FIVE(3) * 3;
// this expands to (3) + 5 * 3, so 5 * 3 is evaluated first
// Now x is 18, not 24!
To fix this, you generally want to surround the whole macro body with parentheses to prevent
the surrounding context from affecting the macro body.
#define ADD_FIVE(a) ((a) + 5)
int x = ADD_FIVE(3) * 3;
On the other hand, if you have a multiline macro that you are using for its side effects, rather
than to compute a value, you probably want to wrap it within curly braces so you don't have
problems when using it following an if statement.
// We use a trick involving exclusive-or to swap two variables
#define SWAP(a, b) a ^= b; b ^= a; a ^= b;
int x = 10;
int y = 5;
// works OK
SWAP(x, y);
2013 Page 3
Ankur Rastogi Unit-V C Pre Processor & Bitwise Operator
int x = 10;
int y = 5;
int z = 4;
int x = 10;
int y = 5;
int z = 4;
Multiline macros
Until now, we've seen only short, one line macros (possibly taking advantage of the
semicolon to put multiple statements on one line.) It turns out that by using a the "\" to
indicate a line continuation, we can write our macros across multiple lines to make them a bit
more readable.
Aside from readability, writing multi-line macros may make it more obvious that you need to
use curly braces to surround the body because it's more clear that multiple effects are
happening at once.
2013 Page 4
Ankur Rastogi Unit-V C Pre Processor & Bitwise Operator
Bitwise Operators in C
Generally, as a programmer you don't need to concern yourself about operations at the bit level.
You're free to think in bytes, or int’s and double’s, or even higher level data types composed of a
combination of these. But there are times when you'd like to be able to go to the level of an
individual bit.
The byte is the lowest level at which we can access data; there's no "bit" type, and we can't ask for
an individual bit. In fact, we can't even perform operations on a single bit -- every bitwise operator
will be applied to, at a minimum, an entire byte at a time. This means we'll be considering the whole
representation of a number whenever we talk about applying a bitwise operator.
The leftshift operator is the equivalent of moving all the bits of a number a specified number of
places to the left:
[variable]<<[number of places]
For instance, consider the number 8 written in binary 00001000. If we wanted to shift it to the left 2
places, we'd end up with 00100000; everything is moved to the left two places, and zeros are added
as padding. This is the number 32 -- in fact, left shifting is the equivalent of multiplying by a power of
two.
But what happens if we shift a number like 128 and we're only storing it in a single byte: 10000000?
Well, 128 * 2 = 256, and we can't even store a number that big in a byte, so it shouldn't be surprising
that the result is 00000000.
It shouldn't surprise you that there's a corresponding right-shift operator: >>. Note that a bitwise
right-shift will be the equivalent of integer division by 2.
Why is it integer division? Consider the number 5, in binary, 00000101. 5/2 is 2.5, but if you are
performing integer division, 5/2 is 2. When you perform a right shift by one: (unsigned int)5>>1, you
end up with 00000010, as the rightmost 1 gets shifted off the end; this is the representation of the
number 2. Note that this only holds true for unsigned integers; otherwise, we are not guaranteed
that the padding bits will be all 0s.
Generally, using the left and right shift operators will result in significantly faster code than
2013 Page 5
Ankur Rastogi Unit-V C Pre Processor & Bitwise Operator
calculating and then multiplying by a power of two. The shift operators will also be useful later when
we look at how to manipulating individual bits.
For now, let's look at some of the other binary operators to see what they can do for us.
Binary AND Operator copies a bit to the result if it (A & B) will give 12 which is 0000
&
exists in both operands. 1100
Binary OR Operator copies a bit if it exists in either (A | B) will give 61 which is 0011
|
operand. 1101
Binary XOR Operator copies the bit if it is set in one (A ^ B) will give 49 which is 0011
^
operand but not both. 0001
Binary Ones Complement Operator is unary and has (~A ) will give -60 which is 1100
~
the effect of 'flipping' bits. 0011
Bitwise AND
The bitwise AND operator is a single ampersand: &. It works on smaller pieces (bits instead of bytes,
chars, integers, etc). A binary AND simply takes the logical AND of the bits in each position of a
number in binary form.
01001000 &
10111000 =
--------
00001000
The most significant bit of the first number is 0, so we know the most significant bit of the result
must be 0; in the second most significant bit, the bit of second number is zero, so we have the same
2013 Page 6
Ankur Rastogi Unit-V C Pre Processor & Bitwise Operator
result. The only time where both bits are 1, which is the only time the result will be 1, is the fifth bit
from the left. Consequently,
72 & 184 = 8
Bitwise OR
Bitwise OR works almost exactly the same way as bitwise AND. The only difference is that only one
of the two bits needs to be a 1 for that position's bit in the result to be 1. (If both bits are a 1, the
result will also have a 1 in that position.) The symbol is a pipe: |. Again, this is similar to boolean
logical operator, which is ||.
01001000 |
10111000 =
--------
11111000
and consequently
72 | 184 = 248
This turns out to be a great way of finding the largest possible value for an unsigned number:
0, of course, is all 0s: 00000000 00000000. Once we twiddle 0, we get all 1s: 11111111 11111111.
Since max is an unsigned int, we don't have to worry about sign bits or twos complement. We know
that all 1s is the largest possible number.
Note that ~ and ! cannot be used interchangeably. When you take the logical NOT of a non-zero
number, you get 0 (FALSE). However, when you twiddle a non-zero number, the only time you'll get
0 is when every bit is turned on.
2013 Page 7
Ankur Rastogi Unit-V C Pre Processor & Bitwise Operator
For instance, if you have two numbers represented in binary as 10101010 and 01110010 then taking
the bitwise XOR results in 11011000. It's easier to see this if the bits are lined up correctly:
01110010 ^
10101010
--------
11011000
You can think of XOR in the following way: you have some bit, either 1 or 0, that we'll call A. When
you take A XOR 0, then you always get A back: if A is 1, you get 1, and if A is 0, you get 0. On the
other hand, when you take A XOR 1, you flip A. If A is 0, you get 1; if A is 1, you get 0.
#include <stdio.h>
main()
{
c = a | b; /* 61 = 0011 1101 */
printf("Line 2 - Value of c is %d\n", c );
c = a ^ b; /* 49 = 0011 0001 */
printf("Line 3 - Value of c is %d\n", c );
2013 Page 8