DMarlon Studio

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.

C · The preprocessor in DMarlon Studio: the example's code and its output in the terminal

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

  • #define replaces text before compiling.
  • In macros, parenthesise each parameter and the whole result.
  • # makes text and ## joins names.
  • Every header gets #ifndef, #define and #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.