Kodewick
Lesson 28 of 30Contents

The preprocessor

Lesson 28 of 30 · Part 6 · Going further · about 15 minutes

Lines that start with # aren't C statements. Before compiling, a tool called the preprocessor goes through your code and handles them: it copies files in and swaps names for text. Only then does the compiler see the result.

main.c
1#include <stdio.h>2 3#define DAYS_IN_WEEK 74 5int main(void) {6    int weeks = 3;7    printf("%d weeks = %d days\n", weeks, weeks * DAYS_IN_WEEK);8    return 0;9}
Output
3 weeks = 21 days

#define DAYS_IN_WEEK 7 tells the preprocessor: "wherever DAYS_IN_WEEK appears, write 7 instead". By the time the compiler gets the code, the line says weeks * 7. The compiler never sees the name at all.

#include

#include <stdio.h> puts the whole contents of the file stdio.h in place of that line. A header like stdio.h holds the prototypes of functions such as printf (lesson 8), so the compiler knows what they look like. Here are the headers you've used so far:

HeaderWhat's in it
<stdio.h>printf, scanf, fopen, fgets, NULL
<string.h>strlen, strcpy, strcmp
<ctype.h>toupper, isdigit
<stdlib.h>malloc, realloc, free
<limits.h>INT_MAX
main.c
1#include <stdio.h>2#include <string.h>3#include <ctype.h>4#include <limits.h>5 6int main(void) {7    char city[] = "lisbon";8    city[0] = toupper(city[0]);9    printf("%s has %zu letters\n", city, strlen(city));10    printf("The largest int is %d\n", INT_MAX);11    return 0;12}
Output
Lisbon has 6 letters
The largest int is 2147483647

Angle brackets < > mean "a header that comes with C". In bigger projects you also write headers of your own and include them with quotes, like #include "contacts.h". The examples on this page are each one file, so they don't need that.

#define for constants

A number that appears in several places is better given a name. When it changes, you change one line:

main.c
1#include <stdio.h>2 3#define SEATS 54#define TICKET_PRICE 125 6int main(void) {7    int booked[SEATS] = {1, 0, 1, 1, 0};8    int sold = 0;9    for (int i = 0; i < SEATS; i++) {10        sold += booked[i];11    }12    printf("%d of %d seats sold\n", sold, SEATS);13    printf("Takings: %d\n", sold * TICKET_PRICE);14    return 0;15}
Output
3 of 5 seats sold
Takings: 36

The names are usually written in capitals, so you can tell them from variables at a glance. And a #define has no = and no ; at the end. It isn't a statement, and the preprocessor copies everything after the name. Put a semicolon there by mistake, and see what happens:

main.c
1#include <stdio.h>2 3#define PRICE 5;4 5int main(void) {6    int total = PRICE * 3;7    printf("%d\n", total);8    return 0;9}
Output
main.c: In function 'main':
main.c:6:23: error: invalid type argument of unary '*' (have 'int')
    6 |     int total = PRICE * 3;
      |                       ^~~

After the swap, line 6 reads int total = 5; * 3;. The ; ends the statement after 5, and * 3; is left over on its own: the compiler reads that * as "follow a pointer", and 3 is not a pointer. The error points at line 6, though the mistake is on line 3. When an error looks impossible, check the #define lines.

Macros with parameters

A #define can take parameters, a bit like a function. This kind is called a macro:

Guess first

What does this print?

1#include <stdio.h>2 3#define SQUARE(x) x * x4 5int main(void) {6    printf("%d\n", SQUARE(2 + 1));7    return 0;8}

SQUARE(4) gives 16 as it should, but with an expression inside, or around it, the result goes wrong:

main.c
1#include <stdio.h>2 3#define SQUARE(x) x * x4 5int main(void) {6    printf("%d\n", SQUARE(4));7    printf("%d\n", SQUARE(2 + 1));8    printf("%d\n", 100 / SQUARE(5));9    return 0;10}
Output
16
5
100

The third line should be 100 / 25, which is 4. But it becomes 100 / 5 * 5: / and * go from left to right, so it's 20 * 5, which is 100.

Parentheses everywhere

The fix is parentheses: around each x, so whatever is passed in stays together, and around the whole thing, so what's outside can't split it:

main.c
1#include <stdio.h>2 3#define SQUARE(x) ((x) * (x))4 5int main(void) {6    printf("%d\n", SQUARE(4));7    printf("%d\n", SQUARE(2 + 1));8    printf("%d\n", 100 / SQUARE(5));9    return 0;10}
Output
16
9
4

Now SQUARE(2 + 1) becomes ((2 + 1) * (2 + 1)) and 100 / SQUARE(5) becomes 100 / ((5) * (5)). All three are right.

There's one trap that parentheses can't fix: x appears twice, so whatever you pass in is worked out twice. SQUARE(n++) adds one to n twice in the same expression, which is undefined behavior. An ordinary function, like int square(int x), has none of these problems. Use macros for small things, and a function when in doubt.

Turning code on and off: #ifdef

The code between #ifdef NAME and #endif is kept only when NAME has been defined with #define. Otherwise the preprocessor removes it before compiling. That's handy for extra output you want only while you're looking for a bug:

main.c
1#include <stdio.h>2 3#define DEBUG4 5int main(void) {6    int prices[4] = {12, 8, 15, 5};7    int total = 0;8    for (int i = 0; i < 4; i++) {9        total += prices[i];10#ifdef DEBUG11        printf("[debug] i=%d total=%d\n", i, total);12#endif13    }14    printf("Total: %d\n", total);15    return 0;16}
Output
[debug] i=0 total=12
[debug] i=1 total=20
[debug] i=2 total=35
[debug] i=3 total=40
Total: 40

Delete the line #define DEBUG and run it again: the [debug] lines are gone, because that printf is no longer in the program at all. #ifndef NAME is the opposite (keep the code when NAME is not defined), and #else gives the other choice:

You writeThe code is kept when
#ifdef NAME ... #endifNAME is defined
#ifndef NAME ... #endifNAME is not defined
#ifdef NAME ... #else ... #endiffirst part if defined, second part if not

const or #define?

For a constant value, a const variable (lesson 2) is usually the better choice. It has a type, the compiler checks it, and it's a real variable: press Step and you'll see TAX_RATE among the variables. SERVICE never shows up, because the compiler only ever saw 3.

main.c
1#include <stdio.h>2 3#define SERVICE 34 5int main(void) {6    const double TAX_RATE = 0.08;7    double meal = 40.0;8    double bill = meal + meal * TAX_RATE + SERVICE;9    printf("Bill: %.2f\n", bill);10    return 0;11}
Output
Bill: 46.20
#defineconst
What it istext the preprocessor swaps ina variable that can't change
Has a typenoyes
Seen when you Stepnoyes
Size of an arrayyesnot for a fixed-size array

C still uses #define for array sizes, like int booked[SEATS] above. For other constants, prefer const.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

Name the numbers

Add #define ADULT 12 and #define CHILD 7 at the top, then use them to print the price for 2 adults and 3 children: Total: 45.

It should print
Total: 45
exercise1.c
1#include <stdio.h>2 3int main(void) {4    int adults = 2;5    int children = 3;6 7    return 0;8}
Output
Press Run or Ctrl+Enter
Exercise 2

Fix the macro

DOUBLE(5) * 2 should be 20, but this prints 15. Fix the macro (not main) so it prints 20.

It should print
20
exercise2.c
1#include <stdio.h>2 3#define DOUBLE(x) x + x4 5int main(void) {6    printf("%d\n", DOUBLE(5) * 2);7    return 0;8}
Output
Press Run or Ctrl+Enter
Exercise 3

Tax only sometimes

Add tax to the price only when WITH_TAX is defined. Use #ifdef, so the program prints 55.00 (and 50.00 once you delete #define WITH_TAX).

It should print
55.00
exercise3.c
1#include <stdio.h>2 3#define WITH_TAX4 5int main(void) {6    const double TAX_RATE = 0.1;7    double price = 50.0;8 9    printf("%.2f\n", price);10    return 0;11}
Output
Press Run or Ctrl+Enter

What you learned

  • The preprocessor handles the # lines before compiling: it copies files in and swaps text.
  • #include <name.h> brings in a header with the prototypes of library functions.
  • #define NAME value swaps NAME for the text after it. There's no = and no ;: a stray ; gets copied too.
  • A macro with parameters swaps in text, not values: put parentheses around each parameter and around the whole thing, ((x) * (x)).
  • #ifdef NAME ... #endif keeps code only when NAME is defined; #ifndef and #else cover the other cases.
  • For constants, const is usually better: it has a type and you can see it when you Step. Array sizes still use #define.