the preprocessor
/ PREE-pro-seh-sor /
Before the real compiler ever looks at your code, a simpler program runs first and does pure text surgery on it. That program is the preprocessor. It does not understand C at all: it does not know what an int is or what a function does. It only knows how to find and copy and paste text according to a handful of commands you write on lines that begin with a # symbol.
Its three main jobs are: textual inclusion (when it sees #include <stdio.h>, it finds that header file and pastes its entire contents in, as if you had typed them there yourself), text substitution (when you write #define PI 3.14159, it replaces every later word PI with 3.14159), and conditional inclusion (with #ifdef and friends, it keeps or throws away whole blocks of text depending on whether a name is defined). The output is a single big slab of pure text with all the # lines gone and all the pasting done. That output is what the compiler proper actually reads.
Because it is blind text replacement, the preprocessor is powerful but dangerous. It happily creates code that looks right but is not, since it ignores all the meaning rules the compiler would enforce. This is why seasoned C and C++ programmers reach for it sparingly, preferring real language features (like const variables and inline functions) when they can, and reserving the preprocessor for the few jobs only it can do, such as including headers and guarding against double inclusion.
#define SQUARE(x) ((x)*(x)) // The preprocessor turns: y = SQUARE(3); // into pure text: y = ((3)*(3)); // BEFORE the compiler sees it
The substitution is textual: the compiler only ever sees the right-hand result, not the macro name.
The preprocessor has no idea about C types, scopes, or semantics; it can produce text that is wrong in ways the compiler then complains about with a confusing message pointing at the expanded code.