Back to Curriculum

Preprocessor Directives

📚 Lesson 9 of 15 ⏱️ 35 min

Preprocessor Directives

35 min

The C preprocessor processes source code before compilation, performing text substitution, file inclusion, and conditional compilation. Preprocessor directives (lines starting with #) are processed by the preprocessor, not the compiler. Understanding the preprocessor enables you to write more flexible, maintainable C code. Preprocessor features include macros, file inclusion, and conditional compilation.

#include directives insert the contents of header files into the source code. System headers (<stdio.h>) are searched in standard directories, while user headers ("myheader.h") are searched in current directory and specified paths. Header files typically contain function declarations, constants, and type definitions. Understanding #include enables code organization and reuse.

#define creates macros that perform text substitution. Simple macros define constants (e.g., #define PI 3.14159), while function-like macros accept arguments (e.g., #define MAX(a, b) ((a) > (b) ? (a) : (b))). Macros are expanded before compilation, so they don't have type checking or scope. Understanding macros enables code generation and constants.

Conditional compilation uses #ifdef, #ifndef, #if, #else, #elif, and #endif to include or exclude code based on conditions. This enables platform-specific code, debug code, and feature flags. Conditional compilation is processed before compilation, so excluded code isn't compiled. Understanding conditional compilation enables flexible, portable code.

Predefined macros like __LINE__, __FILE__, __DATE__, __TIME__, and __STDC__ provide compile-time information. These macros are automatically defined by the compiler and enable debugging, logging, and conditional compilation. Understanding predefined macros enables advanced preprocessor usage.

Best practices for preprocessor include using parentheses in macro definitions to prevent operator precedence issues, using do-while(0) for multi-statement macros, and avoiding complex macros that are better as functions. Understanding best practices prevents common preprocessor pitfalls.

Key Concepts

  • Preprocessor processes code before compilation.
  • #include inserts header file contents.
  • #define creates macros for text substitution.
  • Conditional compilation enables platform-specific code.
  • Predefined macros provide compile-time information.

Learning Objectives

Master

  • Using #include for header files
  • Creating constants and function-like macros with #define
  • Implementing conditional compilation
  • Understanding predefined macros

Develop

  • Understanding compilation process
  • Writing portable, maintainable code
  • Using preprocessor for code generation

Tips

  • Include headers: #include <stdio.h> for system, #include "header.h" for user headers.
  • Define constants: #define CONSTANT value for compile-time constants.
  • Use parentheses in macros: #define MACRO(x) ((x) * 2) to prevent precedence issues.
  • Use conditional compilation: #ifdef DEBUG ... #endif for debug code.

Common Pitfalls

  • Not using parentheses in macros, causing operator precedence errors.
  • Creating complex macros, better implemented as functions.
  • Not understanding macro expansion, causing unexpected behavior.
  • Including headers multiple times, causing redefinition errors.

Summary

  • Preprocessor enables code organization and conditional compilation.
  • Macros provide text substitution before compilation.
  • Conditional compilation enables flexible, portable code.
  • Understanding preprocessor enables advanced C programming.

Exercise

Demonstrate preprocessor directives and macros.

#include <stdio.h>

// Macro definitions
#define PI 3.14159
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define DEBUG 1

// Conditional compilation
#ifdef DEBUG
    #define DEBUG_PRINT(x) printf("DEBUG: %s
", x)
#else
    #define DEBUG_PRINT(x)
#endif

// Function-like macro
#define PRINT_ARRAY(arr, size) do {     for (int i = 0; i < size; i++) {         printf("%d ", arr[i]);     }     printf("
"); } while(0)

int main() {
    // Using constants
    float radius = 5.0;
    float area = PI * SQUARE(radius);
    printf("Area of circle: %.2f
", area);
    
    // Using function-like macros
    int a = 10, b = 20;
    printf("Maximum of %d and %d: %d
", a, b, MAX(a, b));
    
    // Debug macro
    DEBUG_PRINT("This is a debug message");
    
    // Array printing macro
    int numbers[] = {1, 2, 3, 4, 5};
    printf("Array elements: ");
    PRINT_ARRAY(numbers, 5);
    
    // Stringification
    #define STRINGIFY(x) #x
    #define TOSTRING(x) STRINGIFY(x)
    
    int line = __LINE__;
    printf("Current line: %s
", TOSTRING(__LINE__));
    printf("File: %s
", __FILE__);
    printf("Date: %s
", __DATE__);
    printf("Time: %s
", __TIME__);
    
    // Token pasting
    #define CONCAT(a, b) a##b
    
    int var12 = 42;
    printf("Concatenated variable: %d
", CONCAT(var, 12));
    
    return 0;
}

Exercise Tips

  • Use header guards: #ifndef HEADER_H #define HEADER_H ... #endif to prevent multiple inclusion.
  • Use parentheses in macros: #define SQUARE(x) ((x) * (x)) to prevent precedence issues.
  • Use do-while(0) for multi-line macros: ensures proper statement behavior.
  • Use stringification: #define STR(x) #x to convert to string.

Code Editor

Output