Understanding the C Preprocessor: Macros, Conditional Compilation, and File Inclusion

Predefined tokens

__FILE__   // current source filename
__LINE__   // current line number
__DATE__   // compilation date "Mmm dd yyyy"
__TIME__   // compilation time "hh:mm:ss"
__STDC__   // 1 if the compiler conforms to ISO C

Simple textual substitution

#define forever for (;;)
#define CASE    break; case
#define reg     register

Multiline macro

#define printStars()      \
    for (int k = 0; k < 5; ++k) \
        putchar('*');

int main(void)
{
    printStars();
    return 0;
}

Parameterized macros

#define SQR(x) ((x) * (x))

Parentheses are mandatory to avoid operator-precedence surprises:

SQR(3 + 1)   // expands to ((3 + 1) * (3 + 1)) -> 16

Constructing new control-flow keywords

#define DO  do
#define WHILE(x) while (x)

int main(void)
{
    int i = 0;
    DO {
        puts("*");
        ++i;
    } WHILE (i < 5);
    return 0;
}

Macro expansion rules

  1. Scan the actual arguments list; if any argument is itself a macro, expand it first.
  2. Substitute the expanded arguments into the replacement list.
  3. Rescan the result; if new macro names appear, repeat the process.

Macros versus functions

Aspect Macro Function
Overhead Zero call/return cost Call + return instructions
Type safety None (textual replacement) Full type checking
Code size Grows with every use Single definition
Side effects Arguments re-evaluated per use Evaluated once

Example where a macro outperforms a functon:

#define ALLOC(T, n)  ((T *)malloc((n) * sizeof(T)))

int *pi = ALLOC(int, 100);

Side-effect pitfalls

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

int x = 5, y = 7;
int z = MAX(x++, y++);   // x and y incremented twice

Undefining a macro

#undef MAX

Conditional compilation

#if defined(DEBUG)
    #define LOG(msg) printf("DEBUG: %s\n", msg)
#elif LOGLEVEL >= 2
    #define LOG(msg) printf("INFO: %s\n", msg)
#else
    #define LOG(msg)
#endif

File inclusion

#include "config.h"   // search user paths first
#include <stdio.h>    // search system paths

The preprocessor replaces each #include directive with the entire content of the referenced file. If the same header is included by ten translation units, its processed ten times, but the compile-time cost is negligible and has no impact on runtime performance.

Tags: C preprocessor macro conditional-compilation file-inclusion

Posted on Wed, 13 May 2026 05:48:26 +0000 by dancing dragon