CHAPTER 24

image

Preprocessor

The preprocessor is a text substitution tool that modifies the source code before the compilation takes place. This modification is done according to the preprocessor directives that are included in the source files. The directives are easily distinguished from normal programming code in that they all start with a hash sign (#). They must always appear as the first non-whitespace character on a line, and they do not end with a semicolon. The following table shows the preprocessor directives available in C++ along with their functions.

Directive

Description

#include

File include

#define

Macro definition

#undef

Macro undefine

#ifdef

If macro defined

#ifndef

If macro not defined

#if

If

#elif

Else if

#else

Else

#endif

End if

#line

Set line number

#error

Abort compilation

#pragma

Set compiler option

Including Source Files

The #include directive inserts the contents of a file into the current source file. Its most common use is to include header files, both user-defined and library ones. Library header files are enclosed between angle brackets (<>). This tells the preprocessor to search for the header in the default directory where it is configured to look for standard header files.

#include <iostream> // search library directory

Header files that you create for your own program are enclosed within double quotes (""). The preprocessor will then search for the file in the same directory as the current file. In case the header is not found there, the preprocessor will then search among the standard header files.

#include "MyFile.h" // search current, then default

The double quoted form can also be used to specify an absolute or relative path to the file.

#include "C:MyFile.h" // absolute path
#include "..MyFile.h" // relative path

Define

Another important directive is #define, which is used to create compile-time constants, also called macros. After this directive, the name of the constant is specified followed by what it will be replaced by.

#define PI 3.14 // macro definition

The preprocessor will go through and change any occurrences of this constant with whatever comes after it in its definition until the end of the line.

float f = PI; // f = 3.14

By convention, constants should be named in uppercase letters with each word separated by an underscore. That way they are easy to spot when reading the source code.

Undefine

A #define directive should not be used to directly override a previously defined macro. Doing so will give a compiler warning. In order to change a macro, it first needs to be undefined using the #undef directive. Attempting to undefine a macro that is not currently defined will not generate a warning.

#undef PI // undefine
#undef PI // allowed

Predefined Macros

There are a number of macros that are predefined by the compiler. To distinguish them from other macros, their names begin and end with two underscores. These standard macros are listed in the following table.

Directive

Description

__FILE__

Name and path for the current file.

__LINE__

Current line number.

__DATE__

Compilation date in MM DD YYYY format.

__TIME__

Compilation time in HH:MM:SS format.

__func__

Name of the current function. Added in C++11.

A common use for predefined macros is to provide debugging information. To give an example, the following error message includes the file name and line number where the message occurs.

cout << "Error in " << __FILE__ << " at line " << __LINE__;

Macro Functions

Macros can be made to take arguments. This allows them to define compile-time functions. For example, the following macro function gives the square of its argument.

#define SQUARE(x) ((x)*(x))

The macro function is called just as if it was a regular C++ function. Keep in mind that for this kind of function to work, the arguments must be known at compile time.

int x = SQUARE(2); // 4

Note the extra parentheses in the macro definition that are used to avoid problems with operator precedence. Without the parentheses the following example would give an incorrect result, as the multiplication would then be carried out before the addition.

#define SQUARE(x) x*x

int main(void) {
  int x = SQUARE(1+1); // 1+1*1+1 = 3
}

To break a macro function across several lines the backslash character can be used. This will escape the newline character that marks the end of a preprocessor directive. For this to work there must not be any whitespace after the backslash.

#define MAX(a,b)  
a>b ? 
a:b

Although macros can be powerful, they tend to make the code more difficult to read and debug. Macros should therefore only be used when they are absolutely necessary and should always be kept short. C++ code such as constant variables, enum classes, and constexpr functions can often accomplish the same goal more efficiently and safely than #define directives can.

#define DEBUG 0
const bool DEBUG = 0;

#define FORWARD 1
#define STOP 0
#define BACKWARD -1
enum class DIR { FORWARD = 1, STOP = 0, BACKWARD = -1 };

#define MAX(a,b) a>b ? a:b
constexpr int MAX(int a, int b) { return a>b ? a:b; }

Conditional Compilation

The directives used for conditional compilation can include or exclude part of the source code if a certain condition is met. First, there is the #if and #endif directives, which specifies a section of code that will only be included if the condition after the #if directive is true. Note that this condition must evaluate to a constant expression.

#define DEBUG_LEVEL 3

#if DEBUG_LEVEL > 2
 // ...
#endif

Just as with the C++ if statement, any number of #elif (else if ) directives and one final #else directive can be included.

#if DEBUG_LEVEL > 2
 // ...
#elif DEBUG_LEVEL == 2
 // ...
#else
 // ...
#endif

Conditional compilation also provides a useful means of temporarily commenting out large blocks of code for testing purposes. This often cannot be done with the regular multiline comment since they cannot be nested.

#if 0
 /* Removed from compilation */
#endif

Compile if Defined

Sometimes, a section of code should only be compiled if a certain macro has been defined, irrespective of its value. For this purpose two special operators can be used: defined and !defined (not defined).

#define DEBUG

#if defined DEBUG
 // ...
#elif !defined DEBUG
 // ...
#endif

The same effect can also be achieved using the directives #ifdef and #ifndef respectively. For instance, the #ifdef section is only compiled if the specified macro has been previously defined. Note that a macro is considered defined even if it has not been given a value.

#ifdef DEBUG
 // ...
#endif

#ifndef DEBUG
 // ...
#endif

Error

When the #error directive is encountered the compilation is aborted. This directive can be useful to determine whether or not a certain line of code is being compiled. It can optionally take a parameter that specifies the description of the generated compilation error.

#error Compilation aborted

Line

A less commonly used directive is #line, which can change the line number that is displayed when an error occurs during compilation. Following this directive the line number will as usual be increased by one for each successive line. The directive can take an optional string parameter that sets the filename that will be shown when an error occurs.

#line 5 "myapp.cpp"

Pragma

The last standard directive is #pragma, or pragmatic information. This directive is used to specify options to the compiler; and as such, they are vendor specific. To give an example, #pragma message can be used with many compilers to output a string to the build window. Another common argument for this directive is warning, which changes how the compiler handles warnings.

// Show compiler message
#pragma message( "Hello Compiler" )

// Disable warning 4507
#pragma warning(disable : 4507)

Attributes

A new standardized syntax was introduced in C++11 for providing compiler specific information in the source code, so-called attributes. Attributes are placed within double square brackets and may, depending on the attribute, be applied to any code entities. To give an example, a standard attribute added in C++14 is [[deprecated]], which indicates that use of a code entity has become discouraged.

// Mark as deprecated
[[deprecated]] void foo() {}

This attribute allows the compiler to emit a warning whenever such an entity is used. A message can be included in this warning, to describe why the entity has been deprecated.

[[deprecated("foo() is unsafe, use bar() instead")]]
void foo() {}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.240.196