C Preprocessor

C preprocessor (CPP)is an independent phase in the compilation process, inBefore actual compilationText processing of source code. Main features include:

  • Macro expansion
  • File inclusion
  • Conditional Compilation
  • Special directive handling

C PreprocessorIt is not a component of the compiler, but it is a separate step in the compilation process.

In short, the C preprocessor is nothing more than a text replacement tool, which instructs the compiler to complete the required preprocessing before actual compilation.

We will abbreviate the C Preprocessor as CPP.

All preprocessor commands begin with a hash sign#The beginning. It must be the first non-whitespace character. For readability, preprocessor directives should start from the first column.

All important preprocessor directives are listed below:

Directive Description Usage examples
#define Define macros (symbolic constants or function-like macros) #define PI 3.14159
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#include Include Header Files #include <stdio.h>
#include "myheader.h"
#undef Undefine a macro #undef PI
#ifdef Compile the following code if the macro is defined #ifdef DEBUG
printf("Debug info\n");
#endif
#ifndef If the macro is not defined, compile the subsequent code (commonly used for header file protection) #ifndef HEADER_H
#define HEADER_H
/* content */
#endif
#if Conditional compilation (can be used with the defined operator) #if VERSION > 2
/* new version code */
#endif
#else #if/#ifdef/#ifndefthe alternative branch #ifdef WIN32
/* Windows代码 */
#else
/* other systems */
#endif
#elif Similar to else if #if defined(UNIX)
/* Unix代码 */
#elif defined(WIN32)
/* Windows代码 */
#endif
#endif End conditional compilation block As shown in the example above
#error Generate a compilation error and output a message #if !defined(C99)
#error "需要C99标准"
#endif
#pragma Compiler-specific directives (non-standard, vary from compiler to compiler) #pragma once
#pragma pack(1)

Preprocessor examples

Analyze the following examples to understand the different directives.

#define MAX_ARRAY_LENGTH 20

This directive tells CPP to define MAX_ARRAY_LENGTH as 20. Use#defineDefine constants to enhance readability.

#include <stdio.h>
#include "myheader.h"

These directives tell CPP toSystem libraryget stdio.h from it, and add the text to the current source file. The next line tells CPP to get it from the local directorymyheader.h, and add content to the current source file.

#undef  FILE_SIZE
#define FILE_SIZE 42

This directive tells CPP to undefine the defined FILE_SIZE and define it as 42.

#ifndef MESSAGE
   #define MESSAGE "You wish!"
#endif

This directive tells CPP to define MESSAGE only when MESSAGE is not defined.

#ifdef DEBUG
   /* Your debugging statements here */
#endif

This directive tells CPP to execute the processing statements if DEBUG is defined. At compile time, if you pass the switch to the gcc compiler,-DDEBUGthis directive is very useful. It defines DEBUG, and you can enable or disable debugging at any time during compilation.

Example

#include <stdio.h>

// Define a constant macro
#define PI 3.1415926
#define GREETING "Hello, World!"

// Define a function-like macro (note the use of parentheses)
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

// Conditional compilation example
#define DEBUG 1

int main() {
    // Using constant macros
    printf("The value of PI: %f\n", PI);
    printf("%s\n", GREETING);
   
    // Using function-like macros
    int x = 5;
    printf("%d squared is: %d\n", x, SQUARE(x));
    printf("The larger of 3 and 5 is: %d\n", MAX(3, 5));
   
    // Conditional compilation example
    #ifdef DEBUG
    printf("[Debug info] Program reached the main function\n");
    #endif
   
    // Compiler version check
    #if __STDC_VERSION__ >= 201112L
    printf("Using C11 standard\n");
    #elif __STDC_VERSION__ >= 199901L
    printf("Using C99 standard\n");
    #else
    printf("Using C89/C90 standard\n");
    #endif
   
    // Error directive example (uncommenting will cause a compile error)
    // #error "This is a manually triggered error"
   
    return 0;
}

Best practice recommendations

  1. Macro naming:

    • Use all uppercase letters and underscores to name macros
    • Example:#define MAX_SIZE 100
  2. Notes on function-like macros:

    • Every parameter and the entire expression should be enclosed in parentheses.
    • Avoid using arguments with side effects (e.g.SQUARE(x++))
  3. Header file protection:

    #ifndef MY_HEADER_H
    #define MY_HEADER_H
    /* 头文件内容 */
    #endif
  4. Conditional compilation debugging:

    #ifdef DEBUG
    #define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
    #else
    #define DEBUG_PRINT(fmt, ...)
    #endif
    
  5. Cross-Platform Development:

    #if defined(_WIN32)
    // Windows特定代码
    #elif defined(__linux__)
    // Linux特定代码
    #elif defined(__APPLE__)
    // macOS特定代码
    #endif
    

Predefined macros

ANSI C defines many macros. You can use these macros in programming, but you cannot directly modify these predefined macros.

MacroDescription
__DATE__Current date, a character constant in the format "MMM DD YYYY".
__TIME__Current time, a character constant in the format "HH:MM:SS".
__FILE__This contains the current file name, a string constant.
__LINE__This contains the current line number, a decimal constant.
__STDC__Defined as 1 when the compiler compiles in accordance with the ANSI standard.

Let's try the following example:

Example

#include <stdio.h> /** Predefined macro demonstration program * Demonstrates common predefined macros in the ANSI C standard and their uses*/ int main() { // Print the current source file name (string constant) printf("Current file: %s\n", __FILE__); // Print compile date ("MMM DD YYYY" format) printf("Compile date: %s\n", __DATE__); // Print compile time ("HH:MM:SS" format) printf("Compile time: %s\n", __TIME__); // Print current line number (decimal integer) printf("Current line number: %d\n", __LINE__); // Check whether it conforms to ANSI/ISO standard (1 means conforms) printf("ANSI standard: %d\n", __STDC__); // Practical example: debug information output printf("\n[Debug info] %s (line %d) compiled at %s %s\n", __FILE__, __LINE__, __DATE__, __TIME__); return 0; }

When the above code (in filetest.cin) is compiled and executed, it produces the following results:

当前文件: predef_macros.c
编译日期: Jul 5 2023
编译时间: 14:30:45
当前行号: 13
ANSI标准: 1

[调试信息] predef_macros.c (第16行) 编译于 Jul 5 2023 14:30:45

Preprocessor operators

The C preprocessor provides the following operators to help you create macros:

Macro continuation operator (\)

A macro is usually written on a single line. But if a macro is too long to fit on a single line, use the line continuation operator (\). For example:

#define  message_for(a, b)  \
    printf(#a " and " #b ": We love you!\n")

Stringizing operator (#)

In a macro definition, when you need to convert a macro parameter to a string constant, use the stringizing operator (#). The operator used in a macro has a specific parameter or parameter list. For example:

Example

#include <stdio.h> #define message_for(a, b) \ printf(#a " and " #b ": We love you!\n") int main(void) { message_for(Carole, Debra); return 0; }

When the above code is compiled and executed, it produces the following results:

Carole and Debra: We love you!

Token-pasting operator (##)

The token-pasting operator (##) in a macro definition merges two parameters. It allows two separate tokens in the macro definition to be merged into one token. For example:

Example

#include <stdio.h> #define tokenpaster(n) printf ("token" #n " = %d", token##n) int main(void) { int token34 = 40; tokenpaster(34); return 0; }

When the above code is compiled and executed, it produces the following results:

token34 = 40

This is how it happens, because this example will produce the following actual output from the compiler:

printf ("token34 = %d", token34);

This example demonstrates that token##n concatenates into token34; here, we usedStringizing operator (#)andToken-pasting operator (##)。

The defined() operator

PreprocessordefinedThe operator is used in constant expressions to determine whether an identifier has already been defined with #define. If the specified identifier is defined, the value is true (non-zero). If the specified identifier is not defined, the value is false (zero). The following example demonstrates the use of the defined() operator:

Example

#include <stdio.h> #if !defined (MESSAGE) #define MESSAGE "You wish!" #endif int main(void) { printf("Here is the message: %s\n", MESSAGE); return 0; }

When the above code is compiled and executed, it produces the following results:

Here is the message: You wish!

Parameterized macros

A powerful feature of CPP is the ability to use parameterized macros to simulate functions. For example, the following code calculates the square of a number:

int square(int x) {
   return x * x;
}

We can rewrite the above code using a macro as follows:

#define square(x) ((x) * (x))

Before using a macro with parameters, you must use#definea directive to define it. The parameter list is enclosed in parentheses and must immediately follow the macro name. No whitespace is allowed between the macro name and the left parenthesis. For example:

Example

#include <stdio.h> #define MAX(x,y) ((x) > (y) ? (x) : (y)) int main(void) { printf("Max between 20 and 10 is %d\n", MAX(10, 20)); return 0; }

When the above code is compiled and executed, it produces the following results:

Max between 20 and 10 is 20
other extensions