The Preprocessor

The preprocessor is like reviewing blueprints before construction — before actual compilation begins, it expands macros, inserts header files, and prunes conditional branches, then hands the result to the compiler.

Preprocessing Pipeline

The C compilation process: source file → preprocessing → compilation → assembly → linking. The preprocessor runs before compilation, handling all directives that start with #.

You can use the -E flag to see only the preprocessed output:

BASH
gcc -E hello.c -o hello.i

#include Header Files

#include inserts the contents of the specified file verbatim at the current position.

Two Forms

C
#include <stdio.h>
#include "myheader.h"
💡 Tip: Use " " for your own headers and < > for standard library headers — this is the convention.

Common Standard Headers

Header Main Content
stdio.h Input/output functions
stdlib.h Memory allocation, type conversion, random numbers
string.h String operations
math.h Math functions
ctype.h Character classification
assert.h Assert macro

#define Macro Definitions

Object-Like Macros

Define named constants:

C
#define PI 3.14159265
#define MAX_SIZE 100
#define AUTHOR "WebTutorial"

double area = PI * 10 * 10;
int arr[MAX_SIZE];
printf("Author: %s\n", AUTHOR);
⚠️ Note: Do not add a semicolon at the end of a macro definition! If you do, the semicolon becomes part of the substitution and may cause errors.

Function-Like Macros

Macros with parameters look like functions but are text substitutions:

C
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int s = SQUARE(5);
int m = MAX(10, 20);
TEXT
25
20
💡 Tip: The preprocessor does simple text substitution — no type checking, no expression evaluation.

Macro Pitfalls and Parentheses

Pitfall 1: Missing Parentheses Cause Precedence Errors

C
#define DOUBLE(x) x + x
C
int result = DOUBLE(3) * 2;

After expansion this becomes 3 + 3 * 2, which yields 9 instead of the expected 12. Correct version:

C
#define DOUBLE(x) ((x) + (x))

Pitfall 2: Parameters with Side Effects

C
#define SQUARE(x) ((x) * (x))
C
int n = 3;
int s = SQUARE(n++);

After expansion this becomes ((n++) * (n++)), incrementing n twice — undefined behavior.

⚠️ Note: Never pass expressions with side effects (like i++, func()) as macro arguments. Use inline functions or real functions instead.

Pitfall 3: Macros in if-else Branches

C
#define CHECK(cond) if (cond) printf("yes\n")
C
if (flag)
    CHECK(x);
else
    do_something();

After expansion, the else matches the macro's inner if, not the outer one — the classic dangling else problem. Wrap with do { ... } while(0):

C
#define CHECK(cond) do { if (cond) printf("yes\n"); } while(0)

Example

C
#include <stdio.h>

#define SAFE_MAX(a, b) ((a) > (b) ? (a) : (b))
#define SAFE_SQUARE(x) ((x) * (x))
#define SWAP(type, a, b) do { type _tmp = (a); (a) = (b); (b) = _tmp; } while(0)

int main(void) {
    int x = 10, y = 20;
    printf("max: %d\n", SAFE_MAX(x, y));
    printf("square: %d\n", SAFE_SQUARE(5));

    SWAP(int, x, y);
    printf("after swap: x=%d y=%d\n", x, y);

    double a = 3.5, b = 2.1;
    printf("double max: %.1f\n", SAFE_MAX(a, b));
    return 0;
}
▶ Try it Yourself
TEXT
max: 20
square: 25
after swap: x=20 y=10
double max: 3.5

Conditional Compilation

Conditional compilation lets the same source code produce different compiled output under different conditions. It's widely used for cross-platform support, debug switches, and feature trimming.

#if / #elif / #else / #endif

C
#define PLATFORM 2

#if PLATFORM == 1
const char *os = "Windows";
#elif PLATFORM == 2
const char *os = "Linux";
#else
const char *os = "Unknown";
#endif

printf("Platform: %s\n", os);
TEXT
Platform: Linux

#ifdef / #ifndef

#ifdef checks whether a macro is defined; #ifndef checks whether it is not defined.

C
#define DEBUG

#ifdef DEBUG
printf("Debug mode: x=%d\n", x);
#endif

#ifndef BUFFER_SIZE
#define BUFFER_SIZE 256
#endif
💡 Tip: #ifdef DEBUG is equivalent to #if defined(DEBUG), and #ifndef is equivalent to #if !defined(...).

Debug Switch

C
#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s:%d %s\n", __FILE__, __LINE__, msg)
#else
#define LOG(msg)
#endif

LOG("Variable initialized");

Compile with -DDEBUG to enable logging; without it, the program stays silent:

BASH
gcc -DDEBUG -o myapp myapp.c

Example

C
#include <stdio.h>

#define LEVEL 3

int main(void) {
    #if LEVEL >= 3
    printf("Advanced features enabled\n");
    #elif LEVEL >= 2
    printf("Intermediate features enabled\n");
    #else
    printf("Basic features\n");
    #endif

    #ifdef VERBOSE
    printf("Verbose output mode\n");
    #else
    printf("Concise output mode\n");
    #endif
    return 0;
}
▶ Try it Yourself
TEXT
Advanced features enabled
Concise output mode

Predefined Macros

The C standard predefines several useful macros:

Macro Meaning
__FILE__ Current source file name
__LINE__ Current line number
__DATE__ Compilation date
__TIME__ Compilation time
__func__ Current function name (C99)
__STDC__ Whether conforming to ANSI C standard
C
printf("File: %s\n", __FILE__);
printf("Line: %d\n", __LINE__);
printf("Date: %s\n", __DATE__);
printf("Time: %s\n", __TIME__);
printf("Function: %s\n", __func__);
TEXT
File: main.c
Line: 5
Date: Jun 25 2026
Time: 14:30:00
Function: main
💡 Tip: Custom logging macros often combine predefined macros to output file names, line numbers, and other diagnostic information.

#undef to Cancel a Macro

#undef cancels a previous macro definition:

C
#define MAX_SIZE 100
printf("%d\n", MAX_SIZE);
#undef MAX_SIZE

After #undef, MAX_SIZE is no longer a macro and can be redefined or used as a normal identifier.

❓ FAQ

Q Why don't macro definitions end with a semicolon?
A Macros are text substitutions. Adding a semicolon makes it part of the substituted text, which can cause syntax errors in if-else and similar contexts.
Q Function-like macro or inline function — which should I use?
A Inline functions have type checking, evaluate arguments only once, and are easier to debug. Function-like macros are flexible but unsafe. Prefer inline functions; use macros only for special cases like type-generic operations (e.g., SWAP).
Q Is there a difference between #ifdef and #if defined?
A Functionally identical, but #if defined is more flexible — you can combine multiple conditions: #if defined(A) && defined(B). #ifdef can only check a single macro.
Q What's the difference between conditional compilation and if statements?
A Conditional compilation prunes code at the preprocessing stage — unselected branches never enter compilation. With if statements, all branches are compiled; only the runtime path differs. Conditional compilation can exclude code that wouldn't compile (e.g., platform-specific headers).

📖 Summary

📝 Exercises

  1. Define two versions of a SQUARE macro — one with parentheses and one without. Test both with SQUARE(2+3) and observe the result difference
  2. Use #ifdef DEBUG to implement a logging macro LOG(fmt, ...) that outputs the file name and line number in DEBUG mode and produces no output otherwise
  3. Use conditional compilation to implement cross-platform printing: print "Windows platform" on Windows, "Linux platform" on Linux, using #if defined(_WIN32) to detect the platform
100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏