The preprocessor is perhaps best thought of as a separate program that runs before the compiler when you compile your program. It’s purpose is to process directives. Directives are specific instructions that start with a # symbol and end with a newline (NOT a semicolon). There are several different types of directives, which we will cover below. The preprocessor is not smart — it does not understand C++ syntax; rather, it manipulates text before the compiler gets to it.
Includes
You’ve already seen the #include directive in action. #Include tells the preprocessor to insert the contents of the included file into the current file at the point of the #include directive. This is useful when you have information that needs to be included in multiple places (as forward declarations often are).
The #include command has two forms:
#include <filename> tells the compiler to look for the file in a special place defined by the operating system where header files for the runtime library are held.
#include "filename" tells the compiler to look for the file in directory containing the source file doing the #include. If that fails, it will act identically to the angled brackets case.
Macro defines
Macro defines take the form:
#define identifier replacement
Whenever the preprocessor encounters this directive, any further occurrence of ‘identifier’ is replaced by ‘replacement’. The identifier is traditionally typed in all capital letters, using underscores to represent spaces.
For example, consider the following snippet:
#define MY_NAME "Alex" cout << "Hello, " << MY_NAME << endl;
The preprocessor converts this into the following:
#define MY_NAME "Alex" cout << "Hello, " << "Alex" << endl;
Which, when run, prints the output Hello, Alex.
#defines used in this manner have two important properties. First, they allow you to give a descriptive name to something, such as a number.
For example, consider the following snippet:
int nYen = nDollars * 122;
A number such as the 122 in the program above is called a magic number. A magic number is a hard-coded number in the middle of the code that does not have any context — what does 122 mean? Is it a conversion rate? Is it something else? It’s not really clear. In more complex programs, it is often impossible to tell what a hard-coded number represents.
This snippet is clearer:
#define YEN_PER_DOLLAR 122 int nYen = nDollars * YEN_PER_DOLLAR;
Second, #defined numbers can make programs easier to change. Assume the exchange rate changed from 122 to 123, and our program needed to reflect that. Consider the following program:
int nYen1 = nDollars1 * 122; int nYen2 = nDollars2 * 122; int nYen3 = nDollars3 * 122; int nYen4 = nDollars4 * 122; SetWidthTo(122);
To update our program to use the new exchange rate, we’d have to update the first four statements from 122 to 123. But what about the 5th statement? Does that 122 have the same meaning as the other 122s? If so, it should be updated. If not, it should be left alone, or we might break our program somewhere else.
Now consider the same program with #defined values:
#define YEN_PER_DOLLAR 122 #define COLUMNS_PER_PAGE 122 int nYen1 = nDollars1 * YEN_PER_DOLLAR; int nYen2 = nDollars2 * YEN_PER_DOLLAR; int nYen3 = nDollars3 * YEN_PER_DOLLAR; int nYen4 = nDollars4 * YEN_PER_DOLLAR; SetWidthTo(COLUMNS_PER_PAGE);
To change the exchange rate, all we have to do is make one change:
#define YEN_PER_DOLLAR 123 #define COLUMNS_PER_PAGE 122 int nYen1 = nDollars1 * YEN_PER_DOLLAR; int nYen2 = nDollars2 * YEN_PER_DOLLAR; int nYen3 = nDollars3 * YEN_PER_DOLLAR; int nYen4 = nDollars4 * YEN_PER_DOLLAR; SetWidthTo(COLUMNS_PER_PAGE);
Now we’ve updated our yen conversions, and don’t have to worry about inadvertently changing the number of columns per page.
While #define values are a huge improvement over magic numbers, they have some issues of their own, which we will talk more about later when we get into variables and scoping issues.
You can undefine a previously defined value by using the #undef preprocessor directive.
Consider the following snippet:
#define MY_NAME "Alex" cout << "My name is " << MY_NAME << endl; #undef MY_NAME cout << "My name is " << MY_NAME << endl;
The last line of the program causes a compile error because MY_NAME has been undefined.
Conditional compilation
The conditional compilation preprocessor directives allow you to specify under what conditions something will or won’t compile. The only conditional compilation directives we are going to cover in this section are #ifdef, #ifndef, and #endif.
The #ifdef preprocessor directive allow the preprocessor to check whether a value has been previously #defined. If so, the code between the #ifdef and corresponding #endif is compiled. If not, the code is ignored.
Consider the following snippet of code:
#define PRINT_JOE #ifdef PRINT_JOE cout << "Joe" << endl; #endif #ifdef PRINT_BOB cout << "Bob" << endl; #endif
Because PRINT_JOE has been #defined, the line cout << "Joe" << endl; will be compiled. Because PRINT_BOB has not been #defined, the line cout << "Bob" << endl; will not be compiled.
#ifndef is the opposite of #ifdef, in that it allows you to check whether a name has NOT been defined yet.
#ifndef PRINT_BOB cout << "Bob" << endl; #endif
This program prints “Bob”, because PRINT_BOB was never #defined.
Header guards
Because header files can include other header files, it is possible to end up in the situation where a header file gets included multiple times. For example, consider the following program:
add.h:
#include "mymath.h" int add(int x, int y);
subtract.h:
#include "mymath.h" int subtract(int x, int y);
main.cpp:
#include "add.h" #include "subtract.h"
When we include add.h, it brings in both mymath.h and the prototype for add(). When we include subtract.h, it brings in both mymath.h (again) and the prototype for subtract(). Consequently, all the contents of mymath.h will have been included twice, which will cause the compiler to complain.
To prevent this from happening, we use header guards, which are conditional compilation directives that take the following form:
#ifndef SOME_UNIQUE_NAME_HERE #define SOME_UNIQUE_NAME_HERE // your declarations here #endif
When this header is included, the first thing it does is check whether SOME_UNIQUE_NAME_HERE has been previous defined. If this is the first time we’ve included the header, SOME_UNIQUE_NAME_HERE will not have been defined. Consequently, it #defines SOME_UNIQUE_NAME_HERE and includes the contents of the file. If this is not the first time we’ve included the header, SOME_UNIQUE_NAME_HERE will already have been defined from the first time the contents of the header were included. Consequently, the entire header will be ignored.
All of your header files should have header guards on them. SOME_UNIQUE_NAME_HERE can be any name you want, but typically the name of the header file with a _H appended to it is used. For example, add.h would have the header guard:
#ifndef ADD_H #define ADD_H // your declarations here #endif
Even the standard library includes use header guards. If you were to take a look at the iostream header file from Visual Studio 2005 Express, you would see:
#ifndef _IOSTREAM_ #define _IOSTREAM_ // content here #endif
1.11 — Comprehensive quiz
|
Index
|
1.9 — Header files
|
1.11 — Comprehensive quiz
Index
1.9 — Header files
Hello guys,
I just wanted to ask if you have enough room for :
#if defined(macro_name) || defined (macro_name2)
in this tutorial :) I use this quite a lot in coding. Is this a part of the standard by the way?
I have read chapter 0 up to this part and plan to read more. Thanks and more power :)
GovZ, as far as I know, that is an official part of the preprocessor. You should also be able to use && to test whether multiple symbols are defined at the same time:
#if defined (symbol_a) && defined (symbol_b)
I didn’t cover these specific concepts in this tutorial because I don’t even cover what the || and && symbols mean until section 3.6. This is just supposed to be a quick introduction, not a full preprocessor tutorial. :)
There are quite a few other neat things the preprocessor can do that I don’t (and don’t plan to) cover in this tutorial. If you are interested in learning more, there are quite a few preprocessor documents that are publicly available. Here’s one.
This might be a dumb question, but what exactly is the purpose of:
#define [identifier]i.e. a #define without any replacement?
Is it useful mostly in header guards?
I’m not absolutely sure but from what I’ve read here and a book I have, I believe you are correct about it only being for the header guards when there is no replacement.
Billerr, You can use such defines for several reasons. Header guards are the primary one. However, it can be useful to define code that only executes when certain #defines have been set. For example, you might do something like this:
This would only print out nDebugVariable if the program was compiled with the _DEBUG symbol #defined. You might turn this symbol on when compiling a version for development or testing, but turn it off for the release version.
In a game I wrote, I used a #define to toggle whether the game generated random dungeons (for the release version) or a special debug level (for testing/development). I could toggle between the two by commenting/uncommenting the #define and recompiling.
Great!
I have a quick question… what is the benefit of using define as opposed to simply initilializing the variable?
What is the difference between
and
Personally I don’t think there is any reason to use #define this way.
The only place I use #defines is when #defining symbols to use with #ifdef or #ifndef. You can’t use regular variables for those.
Hi, just wanted to point out a little (somewhat misleading) mistake in the page. Close to the top, where you explain the two forms of the #include command the first form should be <filename&rt. It doesn’t display filename at all, at least in my browser (opera), since it’s in angle brackets instead of escape codes and the browser thinks it’s supposed to be an html tag.
Awesome tutorial, the best i’ve read so far. Thanks a lot for sharing your knowledge.
[ Fixed. Thanks for letting me know. -Alex ]
Ok the coding you provided for the header guard is going to be VERY helpful. Thanks for that.