Preprocessor – the #if Directive

This (along with the #elif directive) is probably the second most complicated preprocessor directive because the controlling expression can be complex and include tricky macro replacements.

#if is one of five preprocessor selection statements allowing selection of alternative sections of code for compilation. The other four selection statements are: #ifdef, #ifndef, #elif, and #else.

Behaviour of this preprocessor directive is the same for both C and C++ compilers.

An identifier is a sequence of letters, numbers and underscore characters. Identifiers must begin with a letter or underscore character.

Purpose

The #if directive controls whether the statements found between it and a terminating #endif, #else, or #elif directive are compiled or skipped. The decision is based on the value of the controlling expression.

Format

#if controlling_expression

preprocessor or code statements

#elif or #else or #endif

All preprocessor directives begin with the # symbol. It must be the first character on the line or the first character on the line following optional white space.

Some early compilers flagged an error if # was not the first character on the line.

Spaces or tabs are permitted between the # and if, but not escape characters or other symbols or macros. The preprocessor removes white space and concatenates the # and if together.

The following are valid uses:

#if defined my_macro
# if defined my_macro;
# if     defined    my_macro
# /* comments are white space */ if defined my_macro

The following are invalid uses:

// #\ is not a valid preprocessor directive
# \t if defined my_macro
// #" is not a valid preprocessor directive
# "" if defined my_macro

Rules for the Controlling Expression

The controlling_expression must be made up of: integers, identifiers, macros, character literals, operators, or the preprocessor operator defined. Strings, floating point values, preprocessor directives, the token pasting operator (##), and stringizing operator (#) are invalid and the program is malformed.

The controlling_expression must evaluate to an integer value.

If the controlling expression is non-zero (TRUE), then the statements following the #if until the end of the block (#endif, #elif, or #else) are compiled.

 #if 3
.
.
.
// because the controlling expression is non-zero (TRUE) everything
// until the end of the current block (#elif 42) will be compiled
.
.
.
#elif 42
.
.
.
// everything in here is skipped because the statements in the first
// block were compiled
.
.
.
#else
.
.
.
// all the statements here are skipped because the statements in the
// first block were compiled
.
.
.
#endif

If the controlling expression is zero (FALSE), then the statements following the #if until the end of the block (#endif, #elif, or #else) are skipped.

Integers can be signed or unsigned and have a minimum range equal to long and unsigned long integers, respectively.

Identifiers that are macro names are expanded and evaluated and their evaluation value replaces the macro name.

If the macro name is evaluated by the defined operator, then it is not expanded:

#define MY_MACRO "twas brillig"
// MY_MACRO will never be expanded or evaluated.
// the defined operator determines if the identifier MY_MACRO
// exists. It returns 1 if the macro is defined or 0 if the macro is
// not defined.
#if defined MY_MACRO

Identifiers that are not macro names, are replaced with a value of 0 (zero):

#if SOME_RANDOM_IDENTIFIER

If the identifier SOME_RANDOM_IDENTIFIER was never defined using #define or was undefined using #undef, then it is assigned the value of 0 (zero). To the preprocessor, it looks like:

#if 0

Character literals are converted into integer values. The value of a character literal appearing in either #if or #elif does not have to be the same as the value a character literal would have in a code expression. This is because the character set used by the preprocessor does not have to be the same as the character set used by the compiler.

Depending on the compiler, character literals may or may not be permitted to have negative values.

Any of the arithmetic, relational, logical, or bitwise operators may be used in the controlling_expression. Evaluation precedence is the same as for the language.

The controlling expression may be a simple or complex:

// a simple controlling expression
#if    3
// a more complex controlling expression
#if ( MY_IDENTIFIER > 7 ) && ( VERSION >= 3 )

Since the code is conditionally compiled, it is possible the code never gets compiled and, as a consequence, any errors in it are never caught or noticed.

Any valid language or preprocessor statements may occur within the selection block – even other #if statements. There is no restriction on the level of nesting permitted.

The following:

#if defined MY_MACRO

is equivalent to:

#ifdef MY_MACRO

The following:

#if !defined MY_MACRO

is equivalent to:

#ifndef MY_MACRO

The following:

#if defined MY_MACRO && defined MY_OTHER_MACRO

is equivalent to:

#ifdef MY_MACRO
#  ifdef MY_OTHER_MACRO