Using #define, The Preprocessor and the Compiler in C++

By: Abinaya Viewed: 153192 times    

Every time you run your compiler, your preprocessor runs first. The preprocessor looks for preprocessor instructions, each of which begins with a pound symbol (#). The effect of each of these instructions is a change to the text of the source code. The result is a new source code file, a temporary file that you normally don't see, but that you can instruct the compiler to save so that you can examine it if you want to.

The compiler does not read your original source code file; it reads the output of the preprocessor and compiles that file. You've seen the effect of this already with the #include directive. This instructs the preprocessor to find the file whose name follows the #include directive, and to write it into the intermediate file at that location. It is as if you had typed that entire file right into your source code, and by the time the compiler sees the source code, the included file is there.

Using #define

The #define command defines a string substitution. If you write

#define BIG 512

you have instructed the precompiler to substitute the string 512 wherever it sees the string BIG. This is not a string in the C++ sense. The characters 512 are substituted in your source code wherever the token BIG is seen. A token is a string of characters that can be used wherever a string or constant or other set of letters might be used. Thus, if you write

#define BIG 512
int myArray[BIG];

The intermediate file produced by the precompiler will look like this:

int myArray[512];

Note that the #define statement is gone. Precompiler statements are all removed from the intermediate file; they do not appear in the final source code at all.

Using #define for Constants

One way to use #define is as a substitute for constants. This is almost never a good idea, however, as #define merely makes a string substitution and does no type checking. As explained in the section on constants, there are tremendous advantages to using the const keyword rather than #define.

Using #define for Tests

A second way to use #define, however, is simply to declare that a particular character string is defined. Therefore, you could write

#define BIG

Later, you can test whether BIG has been defined and take action accordingly. The precompiler commands to test whether a string has been defined are #ifdef and #ifndef. Both of these must be followed by the command #endif before the block ends (before the next closing brace).

#ifdef evaluates to TRUE if the string it tests has been defined already. So, you can write

#ifdef DEBUG
cout << "Debug defined";
#endif

When the precompiler reads the #ifdef, it checks a table it has built to see if you've defined DEBUG. If you have, the #ifdef evaluates to TRUE, and everything to the next #else or #endif is written into the intermediate file for compiling. If it evaluates to FALSE, nothing between #ifdef DEBUG and #endif will be written into the intermediate file; it will be as if it were never in the source code in the first place.

Note that #ifndef is the logical reverse of #ifdef. #ifndef evaluates to TRUE if the string has not been defined up to that point in the file.

The #else Precompiler Command

As you might imagine, the term #else can be inserted between either #ifdef or #ifndef and the closing #endif. Listing 17.1 illustrates how these terms are used.

Using #define.

1:     #define DemoVersion
2:     #define DOS_VERSION 5
3:     #include <iostream.h>
4:
5:
6:     int main()
7:     {
8:
9:     cout << "Checking on the definitions of DemoVersion, DOS_VERSION  Â                _and WINDOWS_VERSION...\n";
10:
11:    #ifdef DemoVersion
12:       cout << "DemoVersion defined.\n";
13:    #else
14:       cout << "DemoVersion not defined.\n";
15:    #endif
16:
17:    #ifndef DOS_VERSION
18:       cout << "DOS_VERSION not defined!\n";
19:    #else
20:       cout << "DOS_VERSION defined as: " << DOS_VERSION << endl;
21:    #endif
22:
23:    #ifdef WINDOWS_VERSION
24:       cout << "WINDOWS_VERSION defined!\n";
25:    #else
26:       cout << "WINDOWS_VERSION was not defined.\n";
27:    #endif
28:
29:     cout << "Done.\n";
30:     return 0;
31: }

Output: Checking on the definitions of DemoVersion, DOS_VERSION 
                _and WINDOWS_VERSION...\n";
DemoVersion defined.
DOS_VERSION defined as: 5
WINDOWS_VERSION was not defined.
Done.

Analysis: On lines 1 and 2, DemoVersion and DOS_VERSION are defined, with DOS_VERSION defined with the string 5. On line 11, the definition of DemoVersion is tested, and because DemoVersion is defined (albeit with no value), the test is true and the string on line 12 is printed.
On line 17 is the test that DOS_VERSION is not defined. Because DOS_VERSION is defined, this test fails and execution jumps to line 20. Here the string 5 is substituted for the word DOS_VERSION; this is seen by the compiler as

cout << "DOS_VERSION defined as: " << 5 << endl;

Note that the first word DOS_VERSION is not substituted because it is in a quoted string. The second DOS_VERSION is substituted, however, and thus the compiler sees 5 as if you had typed 5 there.

Finally, on line 23, the program tests for WINDOWS_VERSION. Because you did not define WINDOWS_VERSION, the test fails and the message on line 24 is printed.

Most Viewed Articles (in C++ )

Latest Articles (in C++)

Comment on this tutorial