READMEC Programming

README

Preprocessor / Introduction to Preprocessor

Concept Lesson
Advanced
4 min

Learning Objective

Understand Preprocessor well enough to explain it, recognize it in C Programming, and apply it in a small task.

Why It Matters

This concept is part of the foundation that later lessons and projects assume you already understand.

Table Of ContentsWhat Is The PreprocessorKey CharacteristicsPreprocessor Vs CompilerThe Compilation Pipeline
Private notes
0/8000

Notes stay private to your browser until account sync is configured.

README
3 min read18 headings

Introduction to the C Preprocessor

Table of Contents

  1. What is the Preprocessor?
  2. The Compilation Pipeline
  3. Preprocessor Directives Overview
  4. The #include Directive
  5. Basic Macros with #define
  6. Conditional Compilation
  7. Predefined Macros
  8. Preprocessor Operators
  9. Viewing Preprocessor Output
  10. Best Practices
  11. 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

  1. Text-based transformation: Works with text, not C syntax
  2. Runs before compilation: Compiler never sees preprocessor directives
  3. No type checking: Operates on raw text
  4. 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

ActionDirectiveExample
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

  1. Start with #: Must be first non-whitespace on line
  2. One per line: Each directive on its own line
  3. No semicolon: Directives don't end with ;
  4. 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

DirectivePurpose
#includeInclude header files
#defineDefine macros
#undefUndefine macros
#ifConditional based on expression
#ifdefConditional if macro defined
#ifndefConditional if macro not defined
#elifElse-if for conditionals
#elseElse for conditionals
#endifEnd conditional block
#errorGenerate compile-time error
#warningGenerate compile-time warning (non-standard)
#pragmaCompiler-specific commands
#lineControl 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

ValueStandard
Not definedC89/C90
199409LC95
199901LC99
201112LC11
201710LC17
202311LC23

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

  1. Preprocessor runs before compilation
  2. Operates on text, not C semantics
  3. Directives start with #
  4. #include copies file contents
  5. #define creates text substitutions
  6. 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

OperatorPurposeExample
#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.

Skill Check

Test this lesson

Answer 4 quick questions to lock in the lesson and feed your adaptive practice queue.

--
Score
0/4
Answered
Not attempted
Status
1

Which module does this lesson belong to?

2

Which section is covered in this lesson content?

3

Which term is most central to this lesson?

4

What is the best way to use this lesson for real learning?

Your answers save locally first, then sync when account storage is available.
Practice queue