Reading — step 1 of 7
Learn
Before the C compiler proper sees your code, the preprocessor runs. It handles #include, #define, conditional compilation, and macro expansion. Anything starting with # is a preprocessor directive, NOT C code.
#include — bringing in headers
#include <stdio.h> /* angle brackets — system header, search system paths */
#include "my_header.h" /* double quotes — local header, search current dir first */
At this stage the preprocessor literally PASTES the contents of the included file in place. Headers contain function prototypes, type declarations, and macros so the compiler knows what printf looks like before you call it.
#define — constants and simple macros
#define MAX_SIZE 100
#define PI 3.14159
int buffer[MAX_SIZE]; // becomes int buffer[100];
double area = PI * r * r; // becomes 3.14159 * r * r
Text replacement, NOT a real constant. The preprocessor walks through and substitutes every occurrence.
Macros with arguments:
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int y = SQUARE(5); // becomes ((5) * (5)) = 25
int m = MAX(x, y); // becomes ((x) > (y) ? (x) : (y))
Always wrap arguments AND the whole macro body in parens. Without them:
#define BAD_SQUARE(x) x * x
int y = BAD_SQUARE(2 + 3); // becomes 2 + 3 * 2 + 3 = 11, not 25!
Constants — prefer const or enum
For real constants in modern C, prefer:
const int max_size = 100; // typed, scoped, debugger-friendly
enum { MAX_SIZE = 100 }; // for integer constants
Over #define. Macros bypass type checking and can produce confusing error messages.
Conditional compilation — #ifdef, #ifndef, #if
Selective compilation based on macros:
#ifdef DEBUG
printf("debug: x = %d\n", x);
#endif
#ifndef NDEBUG
assert(condition);
#endif
#if defined(__linux__)
/* Linux-specific code */
#elif defined(_WIN32)
/* Windows-specific code */
#else
#error Unsupported platform
#endif
Used for:
- Platform-specific code paths
- Debug vs release builds (
-DDEBUGon the gcc command line defines DEBUG) - Optional features
- Header guards (next)
Header guards — #ifndef + #define + #endif
Every header file should be wrapped in a guard so it gets included exactly once per translation unit:
#ifndef MY_MODULE_H
#define MY_MODULE_H
/* ... declarations ... */
#endif
Without this, including the header twice (e.g., transitively via different paths) causes "redefinition" errors.
Most compilers also support #pragma once as a non-standard alternative. Both work; #ifndef guards are the portable choice.
Predefined macros
The compiler provides several:
__FILE__— current source file path__LINE__— current line number__func__(C99) — current function name__DATE__,__TIME__— when the file was compiled__STDC_VERSION__— C standard version
fprintf(stderr, "error at %s:%d in %s\n", __FILE__, __LINE__, __func__);
Common mistakes
- Macros without parens around arguments — operator-precedence bugs that look like mysterious wrong answers.
- Side effects inside macro args —
MAX(i++, j)evaluates i++ TWICE. - Forgetting header guards — multiple-definition errors when a header is transitively included.
- Confusing macro arguments with C scope — macros are pure text replacement; they don't see C variables, types, or scopes.
- Using #define for things that should be const — loses type safety, debug visibility.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…