Docs
Preprocessor
Introduction to the C Preprocessor
Table of Contents
- ā¢What is the Preprocessor?
- ā¢The Compilation Pipeline
- ā¢Preprocessor Directives Overview
- ā¢The #include Directive
- ā¢Basic Macros with #define
- ā¢Conditional Compilation
- ā¢Predefined Macros
- ā¢Preprocessor Operators
- ā¢Viewing Preprocessor Output
- ā¢Best Practices
- ā¢Summary
What is the Preprocessor?
The C preprocessor is a text processing tool that runs before the actual compilation of C code. It transforms your source code by handling directives (lines starting with #) before the compiler sees it.
Key Characteristics
- ā¢Text-based transformation: Works with text, not C syntax
- ā¢Runs before compilation: Compiler never sees preprocessor directives
- ā¢No type checking: Operates on raw text
- ā¢Token substitution: Replaces macros with their definitions
Preprocessor vs Compiler
Source File (.c)
ā
ā¼
āāāāāāāāāāāāāāāāāāā
ā Preprocessor ā ā Handles #include, #define, #ifdef, etc.
ā ā Text substitution phase
āāāāāāāāāā¬āāāāāāāāā
ā
ā¼
Translation Unit (Preprocessed .i file)
ā
ā¼
āāāāāāāāāāāāāāāāāāā
ā Compiler ā ā Parses C syntax, generates assembly
ā ā Type checking happens here
āāāāāāāāāā¬āāāāāāāāā
ā
ā¼
Assembly Code (.s)
ā
ā¼
āāāāāāāāāāāāāāāāāāā
ā Assembler ā ā Converts to machine code
āāāāāāāāāā¬āāāāāāāāā
ā
ā¼
Object File (.o)
ā
ā¼
āāāāāāāāāāāāāāāāāāā
ā Linker ā ā Combines objects, resolves symbols
āāāāāāāāāā¬āāāāāāāāā
ā
ā¼
Executable
The Compilation Pipeline
Stage 1: Preprocessing
/* Before preprocessing: main.c */
#include <stdio.h>
#define MAX_SIZE 100
#define DOUBLE(x) ((x) * 2)
int main(void) {
int arr[MAX_SIZE];
int value = DOUBLE(5);
printf("Value: %d\n", value);
return 0;
}
/* After preprocessing (simplified): */
/* Contents of stdio.h expanded here... */
int main(void) {
int arr[100]; /* MAX_SIZE replaced */
int value = ((5) * 2); /* DOUBLE(5) replaced */
printf("Value: %d\n", value);
return 0;
}
What the Preprocessor Does
| Action | Directive | Example |
|---|---|---|
| File inclusion | #include | #include <stdio.h> |
| Macro definition | #define | #define PI 3.14159 |
| Macro undefinition | #undef | #undef DEBUG |
| Conditional compilation | #if, #ifdef, #ifndef | #ifdef DEBUG |
| Line control | #line | #line 100 "file.c" |
| Error generation | #error | #error "Invalid config" |
| Compiler hints | #pragma | #pragma once |
Preprocessor Directives Overview
Syntax Rules
- ā¢Start with #: Must be first non-whitespace on line
- ā¢One per line: Each directive on its own line
- ā¢No semicolon: Directives don't end with
; - ā¢Line continuation: Use
\to continue on next line
/* Directive can have leading whitespace */
#include <stdio.h> /* OK */
/* # can be separated from directive name */
# define VALUE 10 /* OK but unusual */
/* Multi-line directive */
#define LONG_MACRO(a, b, c) \
do { \
printf("%d %d %d\n", a, b, c); \
} while(0)
/* WRONG: No semicolon! */
#include <stdio.h>; /* Error! */
All Preprocessor Directives
| Directive | Purpose |
|---|---|
#include | Include header files |
#define | Define macros |
#undef | Undefine macros |
#if | Conditional based on expression |
#ifdef | Conditional if macro defined |
#ifndef | Conditional if macro not defined |
#elif | Else-if for conditionals |
#else | Else for conditionals |
#endif | End conditional block |
#error | Generate compile-time error |
#warning | Generate compile-time warning (non-standard) |
#pragma | Compiler-specific commands |
#line | Control line numbers and filename |
# (null directive) | Does nothing |
The #include Directive
Two Forms
/* Angle brackets: Search system/standard paths first */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Quotes: Search current directory first, then system paths */
#include "myheader.h"
#include "utils/helper.h"
#include "../common/types.h"
Include Search Paths
#include <header.h>
1. System include directories (e.g., /usr/include)
2. Compiler-specified directories (-I option)
#include "header.h"
1. Directory of the including file
2. Current working directory
3. Same search as <header.h>
What Happens During Include
/* file: myheader.h */
#define MAX 100
void print_hello(void);
/* file: main.c */
#include "myheader.h"
int main(void) {
int arr[MAX];
print_hello();
return 0;
}
/* After preprocessing main.c: */
#define MAX 100
void print_hello(void);
int main(void) {
int arr[MAX];
print_hello();
return 0;
}
Include Guards
Prevent multiple inclusion of same header:
/* file: myheader.h */
#ifndef MYHEADER_H
#define MYHEADER_H
/* Header contents here */
typedef struct {
int x, y;
} Point;
void process_point(Point *p);
#endif /* MYHEADER_H */
Modern alternative (non-standard but widely supported):
#pragma once
/* Header contents */
Basic Macros with #define
Object-like Macros (Constants)
/* Simple constant */
#define PI 3.14159265358979
/* Integer constant */
#define BUFFER_SIZE 1024
/* String constant */
#define APP_NAME "MyApplication"
/* Empty macro (just for conditional compilation) */
#define DEBUG
/* Multi-token replacement */
#define AUTHOR "John Doe", "2024"
/* Usage */
double area = PI * radius * radius;
char buffer[BUFFER_SIZE];
printf("Application: %s\n", APP_NAME);
Function-like Macros
/* Basic function macro */
#define SQUARE(x) ((x) * (x))
/* Multiple parameters */
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
/* With side effects awareness needed! */
#define ABS(x) ((x) < 0 ? -(x) : (x))
/* Usage */
int sq = SQUARE(5); /* Expands to: ((5) * (5)) */
int m = MAX(10, 20); /* Expands to: ((10) > (20) ? (10) : (20)) */
/* DANGER: Side effects! */
int a = 5;
int sq = SQUARE(a++); /* ((a++) * (a++)) - undefined behavior! */
Why Parentheses Matter
/* BAD: No parentheses */
#define DOUBLE(x) x * 2
int result = DOUBLE(3 + 4); /* 3 + 4 * 2 = 11 (wrong!) */
/* GOOD: Proper parentheses */
#define DOUBLE(x) ((x) * 2)
int result = DOUBLE(3 + 4); /* ((3 + 4) * 2) = 14 (correct!) */
Undefining Macros
#define VALUE 100
/* Use VALUE here */
int x = VALUE;
#undef VALUE
/* VALUE is no longer defined */
/* int y = VALUE; */ /* Error! */
/* Can redefine after undef */
#define VALUE 200
int z = VALUE; /* z = 200 */
Conditional Compilation
Basic Conditionals
/* Check if macro is defined */
#ifdef DEBUG
printf("Debug mode enabled\n");
#endif
/* Check if macro is NOT defined */
#ifndef NDEBUG
assert(ptr != NULL);
#endif
/* Using #if with expressions */
#if MAX_SIZE > 100
printf("Large buffer mode\n");
#elif MAX_SIZE > 50
printf("Medium buffer mode\n");
#else
printf("Small buffer mode\n");
#endif
The defined() Operator
/* Equivalent ways to check if defined */
#ifdef DEBUG
/* code */
#endif
#if defined(DEBUG)
/* code */
#endif
/* Can combine with logical operators */
#if defined(DEBUG) && defined(VERBOSE)
printf("Verbose debug mode\n");
#endif
#if defined(WINDOWS) || defined(_WIN32)
/* Windows-specific code */
#endif
#if !defined(HEADER_H)
#define HEADER_H
/* header contents */
#endif
Platform-Specific Code
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#define PLATFORM "Windows"
#elif defined(__linux__)
#include <unistd.h>
#define PLATFORM "Linux"
#elif defined(__APPLE__)
#include <TargetConditionals.h>
#define PLATFORM "macOS"
#else
#define PLATFORM "Unknown"
#endif
int main(void) {
printf("Running on %s\n", PLATFORM);
return 0;
}
Predefined Macros
Standard Predefined Macros
#include <stdio.h>
int main(void) {
/* Current date (MMM DD YYYY) */
printf("Compiled on: %s\n", __DATE__);
/* Current time (HH:MM:SS) */
printf("Compiled at: %s\n", __TIME__);
/* Current source file name */
printf("File: %s\n", __FILE__);
/* Current line number */
printf("Line: %d\n", __LINE__);
/* Current function name (C99) */
printf("Function: %s\n", __func__);
/* STDC version (C95 and later) */
#ifdef __STDC_VERSION__
printf("C Standard: %ld\n", __STDC_VERSION__);
#endif
return 0;
}
Common STDC_VERSION Values
| Value | Standard |
|---|---|
| Not defined | C89/C90 |
| 199409L | C95 |
| 199901L | C99 |
| 201112L | C11 |
| 201710L | C17 |
| 202311L | C23 |
Compiler-Specific Macros
/* GCC version */
#ifdef __GNUC__
printf("GCC version: %d.%d.%d\n",
__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
#endif
/* Clang */
#ifdef __clang__
printf("Clang version: %d.%d\n",
__clang_major__, __clang_minor__);
#endif
/* MSVC */
#ifdef _MSC_VER
printf("MSVC version: %d\n", _MSC_VER);
#endif
Preprocessor Operators
Stringification (#)
Converts macro parameter to string literal:
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
/* Direct stringification */
printf("%s\n", STRINGIFY(hello)); /* Prints: hello */
/* Stringify a macro value (need two levels) */
#define VERSION 2
printf("%s\n", TOSTRING(VERSION)); /* Prints: 2 */
printf("%s\n", STRINGIFY(VERSION)); /* Prints: VERSION */
Token Pasting (##)
Joins two tokens into one:
#define CONCAT(a, b) a##b
int CONCAT(my, Variable) = 10; /* Creates: int myVariable = 10; */
int CONCAT(array, 1) = 20; /* Creates: int array1 = 20; */
/* Useful for generating identifiers */
#define DECLARE_ARRAY(type, name, size) \
type name##_array[size]; \
int name##_count = 0
DECLARE_ARRAY(int, numbers, 100);
/* Creates:
int numbers_array[100];
int numbers_count = 0;
*/
Examples Using Both
#define DEBUG_VAR(var) printf(#var " = %d\n", var)
int count = 42;
DEBUG_VAR(count); /* Prints: count = 42 */
#define ENUM_ENTRY(name) name,
#define ENUM_STRING(name) #name,
/* Auto-generate enum and string array */
#define COLORS(X) \
X(RED) \
X(GREEN) \
X(BLUE)
enum Color { COLORS(ENUM_ENTRY) };
const char *color_names[] = { COLORS(ENUM_STRING) };
/* Expands to:
enum Color { RED, GREEN, BLUE, };
const char *color_names[] = { "RED", "GREEN", "BLUE", };
*/
Viewing Preprocessor Output
Using GCC
# Preprocess only, output to stdout
gcc -E source.c
# Preprocess to file
gcc -E source.c -o source.i
# Preprocess with line markers
gcc -E source.c
# Preprocess without line markers
gcc -E -P source.c
# Show include paths
gcc -v source.c
# Show which headers are included
gcc -H source.c
Using Clang
# Preprocess only
clang -E source.c
# Preprocess to file
clang -E source.c -o source.i
Example
/* file: example.c */
#include <stdio.h>
#define MAX 100
#define DOUBLE(x) ((x) * 2)
int main(void) {
int arr[MAX];
int val = DOUBLE(5);
return 0;
}
$ gcc -E -P example.c
Output (simplified):
/* stdio.h contents here... */
int main(void) {
int arr[100];
int val = ((5) * 2);
return 0;
}
Best Practices
1. Use UPPERCASE for Macro Names
/* GOOD */
#define MAX_BUFFER_SIZE 1024
#define PI 3.14159
/* BAD - Looks like a variable */
#define maxSize 1024
2. Parenthesize Macro Arguments
/* BAD */
#define SQUARE(x) x * x
/* GOOD */
#define SQUARE(x) ((x) * (x))
3. Use do-while(0) for Multi-Statement Macros
/* BAD - Breaks in if-else */
#define LOG(msg) printf("LOG: "); printf(msg);
/* GOOD */
#define LOG(msg) do { \
printf("LOG: "); \
printf(msg); \
} while(0)
/* Usage - works correctly */
if (error)
LOG("An error occurred\n");
else
LOG("All good\n");
4. Prefer inline Functions Over Macros (When Possible)
/* Macro - no type safety */
#define MAX(a, b) ((a) > (b) ? (a) : (b))
/* Inline function - type safe */
static inline int max_int(int a, int b) {
return a > b ? a : b;
}
5. Always Use Include Guards
/* file: header.h */
#ifndef HEADER_H
#define HEADER_H
/* Contents */
#endif /* HEADER_H */
6. Avoid Side Effects in Macro Arguments
#define SQUARE(x) ((x) * (x))
/* DANGEROUS */
int a = 5;
int s = SQUARE(a++); /* Undefined behavior! */
/* SAFE */
int s = SQUARE(a);
a++;
Summary
Key Concepts
- ā¢Preprocessor runs before compilation
- ā¢Operates on text, not C semantics
- ā¢Directives start with
# - ā¢
#includecopies file contents - ā¢
#definecreates text substitutions - ā¢Conditional directives enable/disable code sections
Common Directives Quick Reference
/* File inclusion */
#include <header.h>
#include "header.h"
/* Macro definition */
#define NAME value
#define FUNC(x) ((x) + 1)
#undef NAME
/* Conditionals */
#if expression
#ifdef MACRO
#ifndef MACRO
#elif expression
#else
#endif
/* Other */
#error "message"
#pragma directive
#line number "filename"
Preprocessor Operators
| Operator | Purpose | Example |
|---|---|---|
# | Stringification | #define STR(x) #x |
## | Token pasting | #define CONCAT(a,b) a##b |
defined() | Check if defined | #if defined(DEBUG) |
Next Steps
After understanding preprocessor basics:
- ā¢Learn about advanced macro techniques
- ā¢Study conditional compilation patterns
- ā¢Explore header file organization
- ā¢Practice with real-world projects
This tutorial is part of the C Programming Learning Series.