Preprocessor
C preprocessor directives — includes, defines, conditional compilation
Introduction
In this lesson, you'll learn about preprocessor in C. Coming from Python, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In Python, you're familiar with c preprocessor directives — includes, defines, conditional compilation.
C has its own approach to c preprocessor directives — includes, defines, conditional compilation, which we'll explore step by step.
The C Way
Let's see how C handles this concept. Here's a typical example:
// #include — textual file insertion (like Python import)
#include <stdio.h> // system header (angle brackets)
#include <stdlib.h>
#include "mymodule.h" // local header (quotes)
// #define — symbolic constants (no type, no semicolon!)
#define MAX_SIZE 1024
#define PI 3.14159265358979
// Function-like macro (use parentheses everywhere!)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
// Include guard — prevents double-inclusion in headers
// mymodule.h:
#ifndef MYMODULE_H
#define MYMODULE_H
void my_function(void);
typedef struct { int x; } MyStruct;
#endif // MYMODULE_H
// Conditional compilation
#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s\n", msg)
#else
#define LOG(msg) // empty — compiled out
#endif
// Platform detection
#ifdef _WIN32
#define PATH_SEP "\\"
#else
#define PATH_SEP "/"
#endif
int main(void) {
printf("max: %d\n", MAX_SIZE);
int x = MAX(3, 7); // expands inline
LOG("starting"); // expands to printf or nothing
return 0;
}Comparing to Python
Here's how you might have written similar code in Python:
# Imports
import os
import sys
from math import pi, sqrt
from typing import TYPE_CHECKING
# Constants
MAX_SIZE = 1024
DEBUG = False
# Conditional imports
if TYPE_CHECKING:
from mymodule import MyClass # only for type checkers
# Platform check
import platform
if platform.system() == "Windows":
SEP = "\\"
else:
SEP = "/"
# Version check
import sys
if sys.version_info < (3, 10):
raise RuntimeError("Need Python 3.10+")You may be used to different syntax or behavior.
#include is textual insertion — not a scoped import like Python's import
You may be used to different syntax or behavior.
#define creates text substitutions — no type checking, no scoping
You may be used to different syntax or behavior.
Include guards (#ifndef/#define/#endif) prevent double-inclusion
You may be used to different syntax or behavior.
Conditional compilation (#ifdef) removes code at compile time — not runtime if/else
You may be used to different syntax or behavior.
Function-like macros are dangerous — always parenthesize arguments and result
Step-by-Step Breakdown
1. #include
The preprocessor literally copies the content of the included file. Angle brackets search system paths; quotes search locally first.
import os
from math import sqrt#include <math.h> // system: /usr/include/math.h
#include "utils.h" // local: ./utils.h then system2. #define Constants
#define creates simple text substitutions. Use ALL_CAPS by convention. Add no semicolon — the preprocessor doesn't understand C syntax.
MAX_SIZE = 1024 # Python constant (by convention)#define MAX_SIZE 1024
// Better (typed): const int MAX_SIZE = 1024; // C99+
// or: enum { MAX_SIZE = 1024 };3. Include Guards
Include guards prevent a header from being processed twice. Every .h file should have one.
# Python: no double-import problem — modules are cached// top of every .h file:
#ifndef MY_HEADER_H
#define MY_HEADER_H
// ... declarations ...
#endif
// Or: #pragma once (non-standard but widely supported)4. Conditional Compilation
#ifdef removes entire code blocks at compile time. Use for debug builds, platform-specific code, or feature flags.
DEBUG = os.getenv("DEBUG", "0") == "1"
if DEBUG:
print("debug info")// Compile with: gcc -DDEBUG main.c
#ifdef DEBUG
printf("x = %d\n", x);
#endif
// Platform-specific
#ifdef _WIN32
windows_specific_code();
#elif __linux__
linux_specific_code();
#endifCommon Mistakes
When coming from Python, developers often make these mistakes:
- #include is textual insertion — not a scoped import like Python's import
- #define creates text substitutions — no type checking, no scoping
- Include guards (#ifndef/#define/#endif) prevent double-inclusion
Key Takeaways
- #include copies file content; angle brackets for system, quotes for local
- #define creates untyped text substitutions; prefer const/enum for typed constants
- Include guards (#ifndef/#define/#endif) in every .h file to prevent double-inclusion
- #ifdef for conditional compilation — code removed entirely, not just skipped at runtime