Lesson 15 · 15 of 20
The preprocessor · C
Use the C preprocessor: constants and macros with parameters, # and ##, conditional compilation with #if, and include guards in headers.

Before compiling, the preprocessor handles the lines that start with #, and it only works with text. #define VAT_RATE 0.21 replaces every VAT_RATE in the code with 0.21: it is not a variable, takes no memory and has no type.
Macros with parameters are text substitution too, and that has a catch. SQUARE_BAD(1 + 2) becomes 1 + 2 * 1 + 2, which is 5, not 9. The correct version wraps each parameter and the whole result in parentheses: ((x) * (x)). Even so, an argument like i++ would be evaluated twice; for anything more complex, a function is better.
`#` turns an argument into text: SHOW(expr) prints the expression itself and its value. `##` glues two pieces into one name: FIELD(stock) declares item_stock. The predefined macros __FILE__, __LINE__ and __STDC_VERSION__ give the file, the line and the C version (201112 is C11).
`#if` compiles one piece or another depending on a condition settled before compiling: with DEBUG set to 1 the debug message goes in, and the #else branch never even reaches the compiler. DEBUG is usually defined at build time, with -DDEBUG=1, without touching the code.
An include guard wraps the whole content of a .h header: the first time, SHOP_H is not defined, so it gets defined and the content goes in; if the header is included again, #ifndef SHOP_H is false and it is skipped. The example shows it with the same struct item written twice: without the guard, defining it twice would be an error.
Code · main.c
#include <stdio.h>// Include guard: normally the whole body of a header file such as shop.h.#ifndef SHOP_H // first time: SHOP_H is not defined yet#define SHOP_Hstruct item { const char *name; double price; };#endif#ifndef SHOP_H // shop.h included again: now it is skipped,struct item { const char *name; double price; }; // or this would be an error#endif#define VAT_RATE 0.21 // replaced by its text before compiling#define SQUARE_BAD(x) x * x // no parentheses: a classic bug#define SQUARE(x) ((x) * (x))#define MAX(a, b) ((a) > (b) ? (a) : (b))#define SHOW(expr) printf("%-17s = %g\n", #expr, (double)(expr)) // # makes text#define FIELD(name) item_##name // ## glues tokens#define DEBUG 1int main(void) { struct item tea = {"Green tea", 4.50}; printf("%s with VAT: %.2f\n", tea.name, tea.price * (1 + VAT_RATE)); SHOW(SQUARE_BAD(1 + 2)); // becomes 1 + 2 * 1 + 2 SHOW(SQUARE(1 + 2)); SHOW(MAX(7, 12)); int FIELD(stock) = 40; // declares item_stock printf("item_stock = %d\n", item_stock);#if DEBUG printf("[debug] %s, line %d, C standard %ld\n", __FILE__, __LINE__, __STDC_VERSION__);#else printf("Release build\n");#endif return 0;}Output · cc main.c -o main && ./main
Green tea with VAT: 5.45 SQUARE_BAD(1 + 2) = 5 SQUARE(1 + 2) = 9 MAX(7, 12) = 12 item_stock = 40 [debug] main.c, line 29, C standard 201112
Key points
#definereplaces text before compiling.- In macros, parenthesise each parameter and the whole result.
#makes text and##joins names.- Every header gets
#ifndef,#defineand#endif.
Exercise
Set DEBUG to 0 and check the output; then write a CLAMP(x, lo, hi) macro that keeps x between lo and hi and try it with SHOW.