Strictly speaking compiling involves two distinct processes: preprocessing and then compiling. Preprocessing involves the interpretation of preprocessor directives. These start with the # character and modify the source that has to be compiled. Some of the most important are:-
include inserts the contents of a specified file (often called a header file) at the current point in the file. For example:-
#include "MyPackage/MyClass.h" #include <iostream>
The preprocessor looks for the named file in the directory containing
the file that is being compiled, in the first example above it will look
in this directory for the directory MyPackage
and in that
directory for the file MyClass.h
. If the search fails it will
search in other directories as well. In the second example above the
angle brackets means that this is a standard header so the preprocessor
must look in standard system directories for the file iostream
.
This define a macro, a symbol that will subsequently be replaced by its associated value. For example:-
#define MAXCOUNT 100 ... if ( count >= MAXCOUNT ) return;The symbol MAXCOUNT is defined to have the associated value of the string 100. Wherever the symbol MAXCOUNT appears following is definition it will be replaced by 100.
Macros can take formal arguments which are replaced by the actual arguments when the macro is used. For example:-
#define PRINT(x,y) cout << (x) << (y) << endl; ... PRINT(number1,number2)generates the code:-
cout << (number1) << (number2) << endl;
Aside: macros taking arguments are generally considered to be bad practice because the compiler is passed code that can be quite different to that in the source file. Furthermore it's easy to write macros that don't work as intended and produce syntactically correct but semantically flawed code. They are particularly deprecated in C++ as the language almost invariably provides safer and more powerful alternatives.
These directives can be used to get the preprocessor to skip lines of source to the corresponding #endif. A very common application of #ifdef ensures headers are not included multiple times. The reason that it is necessary to guard against this is because header files can be nested i.e. can contain #include directives. If a file includes several headers that include, either directly or indirectly, some common header, then it will be included multiple times unless steps are taken to prevent it.
For example
suppose we have the header file MyClass.h
. It could start:-
#ifndef MYCLASS_H #define MYCLASS_H .. #endif
This tells the preprocessor that if the macro MYCLASS_H
is not
defined, define it and include the lines up to #endif, otherwise
skip directly to #endif.
Then when the directive:-
#include "MyClass.h"
is processed, assuming the MYCLASS_H
macro is not defined
elsewhere, the macro will be defined (the associated value is a null
string) and the remainder of the file is included. However when next
included the #ifndef fails and the file is skipped.
The #ifeq allows for expression testing. There are also #elif and #else directives for example:-
#if OPT==4 #include "header4.h" #elif OPT>0 #include "someheader.h" #else #include<cstlib> #endif
Its also possible to nest conditional blocks.
Preprocessor directives appear in almost all C/C++ code. They can also be used in Fortran so long as the compiler supports preprocessing, but not all compilers do.