Step 1 of 7 · Reading · ~4 min
Learn
File I/O and the Preprocessor
The Preprocessor
Before the compiler sees a line of your C, a separate program rewrites it. The preprocessor obeys every line that starts with #: it pastes files in, substitutes text and deletes whole regions. By the end of this lesson you can write function-like macros that survive being handed expressions, and you will know why most macro bugs are precedence bugs.
#include pastes a file
#include <stdio.h> /* angle brackets: search the system include paths */
#include "grid.h" /* quotes: look next to this source file first */
It literally pastes the file's text. That is how the compiler knows the shape of printf before you call it.
#define substitutes text, not values
#define LIMIT 64
#define SQ(x) ((x) * (x))
int slots[LIMIT]; /* becomes: int slots[64]; */
int s = SQ(2 + 3); /* becomes: ((2 + 3) * (2 + 3)) -> 25 */
An object-like macro is a name swapped for text. A function-like macro also swaps its arguments in as text, character for character. Nothing is evaluated first, so the parentheses do all the work. Watch each one fail when it is missing, with n = 4:
| Body of SQ | Use | Expands to | Result |
|---|---|---|---|
x * x | SQ(n + 1) | n + 1 * n + 1 | 9, not 25 |
(x) * (x) | 100 / SQ(5) | 100 / (5) * (5) | 100, not 4 |
((x) * (x)) | both | fully grouped | 25 and 4 |
The first row loses because * binds tighter than + inside the argument. The second row loses because the operator outside the macro grabs only the first piece of the body. So: parenthesise every use of a parameter and the whole body.
The same rule covers the ternary operator cond ? a : b, which evaluates to a when cond is true and to b otherwise, and has very low precedence:
#define ABS_BAD(x) (x) < 0 ? -(x) : (x)
#define ABS(x) ((x) < 0 ? -(x) : (x))
int k = -3;
ABS_BAD(k) + 10 /* (k) < 0 ? -(k) : (k) + 10 -> 3 */
ABS(k) + 10 /* ((k) < 0 ? -(k) : (k)) + 10 -> 13 */
Two more consequences of "it is only text". A trailing semicolon rides into every expansion, so #define SQ(x) ((x) * (x)); breaks printf("%d", SQ(n)); with expected ')' before ';' token. And because arguments are pasted rather than evaluated, SQ(i++) increments i twice.
Prefer const and enum for plain constants
const int limit = 64; /* typed, scoped, visible in a debugger */
enum { LIMIT = 64 }; /* an integer constant, also typed */
Both live inside the type system; a #define does not, so its errors are reported against expanded text you never wrote. Keep macros for what only text substitution can do: function-like macros and conditional compilation.
Conditional compilation and header guards
#ifdef DEBUG /* switched on by -DDEBUG on the gcc line */
fprintf(stderr, "k = %d at %s:%d\n", k, __FILE__, __LINE__);
#endif
#ifdef, #ifndef and #if defined(...) keep or delete regions before compilation. __FILE__ and __LINE__ are macros the compiler defines for you.
A header included twice in one file redefines everything in it, so headers guard themselves:
#ifndef GRID_H
#define GRID_H
/* declarations */
#endif
The first inclusion defines GRID_H and keeps the body; later ones skip to #endif. #pragma once does the same in one line but is not standard.
Common mistakes
- Bare parameters (
x * xstyle): an argument like3 + 1splits apart inside a cube, giving 3 + 13 + 13 + 1 = 10 instead of 64. - No outer parentheses: in
SQ(5) % 3style uses the outside operator binds to the last factor only, so a remainder comes out as a large number. - Unwrapped ternary: a
+ 1after the macro attaches to the last branch, so whenever the first branch wins you get the value without the+ 1. - Trailing semicolon on the
#define: a compile error at every use.
Your exercise
Replace the two placeholder macros, CUBE(x) and MAX(a, b). main is written for you and deliberately passes expressions like a + b and uses the results inside larger expressions, so only fully parenthesised macros pass.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…