Skip to main content

Introduction to the C Preprocessor

The C preprocessor is a text substitution tool that runs before the actual compilation begins. It processes all lines beginning with # and performs text replacements, file inclusions, and conditional compilation.
The preprocessor doesn’t understand C syntax - it only performs text manipulation. This means macros work purely through textual substitution.

Preprocessor Directives

All preprocessor directives start with # and must appear at the beginning of a line (though whitespace is allowed before the #). Common directives:
  • #include - Include files
  • #define - Define macros
  • #undef - Undefine macros
  • #ifdef, #ifndef - Conditional compilation
  • #if, #elif, #else, #endif - More complex conditions
  • #error - Generate compilation error
  • #pragma - Compiler-specific commands

Header Guards

Header guards prevent multiple inclusion of the same header file:
#ifndef SIZE
#define SIZE 1024
#endif
How it works:
  1. First inclusion: SIZE is not defined, so #define SIZE 1024 executes
  2. Second inclusion: SIZE is already defined, so the block is skipped
  3. This prevents redefinition errors
Always use header guards in your header files. Without them, including the same file multiple times causes redefinition errors.

Object-like Macros

Object-like macros are simple text replacements without parameters.

Defining Constants

#ifndef SIZE
#define SIZE 1024
#endif
Every occurrence of SIZE in your code will be replaced with 1024 before compilation.

Mathematical Constants

#ifndef PI
#define PI 3.14159265359
#endif
Usage example:
#include <stdio.h>
#include "1-pi.h"

int main(void)
{
    float area;
    float radius = 5.0;
    
    area = PI * radius * radius;
    printf("Area: %.2f\n", area);
    return (0);
}

Benefits of Object-like Macros

Maintainability

Change the value in one place, and it updates everywhere

Readability

SIZE is more meaningful than a magic number like 1024

Function-like Macros

Function-like macros take parameters and perform text substitution with those parameters.

Absolute Value Macro

#ifndef ABS_
#define ABS_
#define ABS(x) ((x) < 0 ? -(x) : (x))
#endif
Key points:
  • Parameters are enclosed in parentheses: (x)
  • The entire macro is enclosed in parentheses
  • Uses the ternary operator for conditional logic
Usage:
int num = -42;
int positive = ABS(num);  // Expands to: ((num) < 0 ? -(num) : (num))
printf("%d\n", positive); // Output: 42

Simple Arithmetic Macro

#ifndef SUM_
#define SUM_
#define SUM(x, y) ((x) + (y))
#endif
Usage:
int result = SUM(5, 3);           // Expands to: ((5) + (3))
int complex = SUM(2 * 3, 4 + 1);  // Expands to: ((2 * 3) + (4 + 1))
Always parenthesize macro parameters!❌ Wrong: #define SUM(x, y) x + y
int result = SUM(2, 3) * 4;  // Expands to: 2 + 3 * 4 = 14 (wrong!)
✅ Correct: #define SUM(x, y) ((x) + (y))
int result = SUM(2, 3) * 4;  // Expands to: ((2) + (3)) * 4 = 20 (correct!)

Predefined Macros

C provides several predefined macros that are automatically available:

__FILE__ - Current File Name

#include <stdio.h>

/**
 * main - prints the name of the file it was compiled from,
 * followed by a new line
 * Return: (0) on success
 */
int main(void)
{
    printf("%s\n", __FILE__);
    return (0);
}
Output: (if the file is named 2-main.c)
2-main.c

Other Predefined Macros

MacroDescriptionExample
__LINE__Current line number42
__DATE__Compilation date"Mar 03 2026"
__TIME__Compilation time"12:34:56"
__FUNCTION__Current function name"main"
Practical use case:
#define DEBUG_PRINT(msg) \
    printf("[%s:%d] %s\n", __FILE__, __LINE__, msg)

int main(void)
{
    DEBUG_PRINT("Starting program");
    // Output: [main.c:5] Starting program
    return (0);
}

Macro vs Function

Macros

Advantages:
  • No function call overhead
  • Type-independent
  • Inline expansion
Disadvantages:
  • Larger code size
  • No type checking
  • Hard to debug
  • Side effects with complex expressions

Functions

Advantages:
  • Type checking
  • Easier to debug
  • Can use pointers and addresses
  • Smaller code size
Disadvantages:
  • Function call overhead
  • Type-specific
  • Not inline (unless specified)

Macro Pitfalls

Side Effects

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

int num = 5;
int result = SQUARE(num++);  // Dangerous!
// Expands to: ((num++) * (num++))
// num is incremented twice!
Never use expressions with side effects in macros!Expressions with side effects include:
  • Increment/decrement operators (++, --)
  • Assignment operators (=, +=, etc.)
  • Function calls that modify state

Solution

int num = 5;
int temp = num++;
int result = SQUARE(temp);  // Safe

Multi-line Macros

For complex macros spanning multiple lines, use backslash (\) for line continuation:
#define PRINT_ERROR(msg) \
    do { \
        fprintf(stderr, "Error in %s:%d: %s\n", \
                __FILE__, __LINE__, msg); \
    } while (0)
The do { ... } while (0) pattern ensures the macro behaves like a single statement and works correctly with if-else blocks.

Conditional Compilation

Debug Builds

#ifdef DEBUG
    printf("Debug: x = %d\n", x);
#endif
Compile with debug:
gcc -DDEBUG program.c

Platform-specific Code

#ifdef __linux__
    // Linux-specific code
#elif defined(_WIN32)
    // Windows-specific code
#else
    // Generic code
#endif

Best Practices

  1. Use UPPERCASE for macro names
    #define MAX_SIZE 100  // Good
    #define max_size 100  // Bad - looks like a variable
    
  2. Always use header guards
    #ifndef MY_HEADER_H
    #define MY_HEADER_H
    // Header contents
    #endif
    
  3. Parenthesize everything in macros
    #define MULTIPLY(a, b) ((a) * (b))  // Good
    #define MULTIPLY(a, b) a * b        // Bad
    
  4. Prefer inline functions for complex logic
    // For simple operations, macros are fine
    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    
    // For complex logic, use inline functions
    static inline int complex_calc(int x, int y)
    {
        // Multiple lines of logic
        return result;
    }
    
  5. Document your macros
    /**
     * ABS - Calculate absolute value
     * @x: The number to process
     *
     * Returns the absolute value of x.
     * Warning: Do not use with expressions that have side effects.
     */
    #define ABS(x) ((x) < 0 ? -(x) : (x))
    

Common Use Cases

Array Size

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

int numbers[] = {1, 2, 3, 4, 5};
int count = ARRAY_SIZE(numbers);  // count = 5

Min/Max

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

Bit Operations

#define SET_BIT(n, pos) ((n) |= (1 << (pos)))
#define CLEAR_BIT(n, pos) ((n) &= ~(1 << (pos)))
#define TOGGLE_BIT(n, pos) ((n) ^= (1 << (pos)))

Key Takeaways

  • The preprocessor performs text substitution before compilation
  • Use object-like macros for constants
  • Use function-like macros for simple operations
  • Always parenthesize macro parameters and the entire macro
  • Avoid side effects in macro arguments
  • Use header guards in all header files
  • Prefer inline functions for complex logic

Bit Manipulation

Learn about bitwise operations

Structures & Typedef

Master custom data types

Build docs developers (and LLMs) love