The preprocessor
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.
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}
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:
| Header | What'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 |
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}
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:
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}
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:
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}
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:
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}
A macro doesn't work out 2 + 1 first. It swaps in the text: SQUARE(2 + 1) becomes 2 + 1 * 2 + 1. * goes before +, so that's 2 + 2 + 1, which is 5.
SQUARE(4) gives 16 as it should, but with an expression inside, or around it, the result goes wrong:
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}
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:
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}
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:
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}
[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 write | The code is kept when |
|---|---|
#ifdef NAME ... #endif | NAME is defined |
#ifndef NAME ... #endif | NAME is not defined |
#ifdef NAME ... #else ... #endif | first 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.
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}
Bill: 46.20
#define | const | |
|---|---|---|
| What it is | text the preprocessor swaps in | a variable that can't change |
| Has a type | no | yes |
| Seen when you Step | no | yes |
| Size of an array | yes | not 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.
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.
Total: 45
1#include <stdio.h>2 3int main(void) {4 int adults = 2;5 int children = 3;6 7 return 0;8}
Fix the macro
✓DOUBLE(5) * 2 should be 20, but this prints 15. Fix the macro (not main) so it prints 20.
20
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}
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).
55.00
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}
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 valueswapsNAMEfor 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...#endifkeeps code only whenNAMEis defined;#ifndefand#elsecover the other cases.- For constants,
constis usually better: it has a type and you can see it when you Step. Array sizes still use#define.