Constants, enums and macros

Intermediate

Introduction

Seeing if (status == 3) tells a reader nothing, and changing a limit that was typed in twenty places is a recipe for mistakes. C gives you three ways of naming fixed values. Each has its own purpose, and the differences matter.

How it works

const makes a variable that cannot be changed. It has a type and an address, and the compiler checks how it is used.

#define is handled by the preprocessor, a stage that runs before the compiler. It replaces each use of a name with the text you gave. #include is also a preprocessor command: it pastes in the contents of a header file.

#define MAX 100

No semicolon is used, because the whole rest of the line becomes the replacement.

Macros with arguments look like functions but are still only text replacement. That leads to a classic trap. With #define SQUARE(x) x * x, the use SQUARE(2 + 3) becomes 2 + 3 * 2 + 3, which is 11 instead of 25. The fix is brackets around each argument and around the whole thing: #define SQUARE(x) ((x) * (x)).

A macro also repeats its argument, so SQUARE(i++) would add to i twice. A normal function does not have that problem, and it should be your first choice.

enum creates a set of named whole numbers, starting from 0 unless you say otherwise. It suits a small set of choices, such as days or states:

#include <stdio.h>

typedef enum { RED, YELLOW, GREEN } Light;

const char *name_of(Light l) {
    switch (l) {
    case RED:
        return "red";
    case YELLOW:
        return "yellow";
    default:
        return "green";
    }
}

int main(void) {
    Light now = RED;
    for (int i = 0; i < 4; i++) {
        printf("%s\n", name_of(now));
        now = (now + 1) % 3;
    }
    return 0;
}
Open in compiler
ToolHandled byHas a type?Best for
constthe compileryesfixed values in code
#definethe preprocessornoarray sizes in older code, conditional compiling
enumthe compileryes, integera set of related choices

Syntax

#define SIZE 10
#define MAX(a, b) ((a) > (b) ? (a) : (b))
const double RATE = 0.15;
enum colour { RED, GREEN = 5, BLUE };   // BLUE is 6

Example

#include <stdio.h>

#define MAX_STUDENTS 3
#define SQUARE(x) ((x) * (x))

enum day { MON, TUE, WED };

int main(void) {
    const int limit = 10;
    int marks[MAX_STUDENTS] = {70, 80, 90};
    enum day today = WED;
    printf("%d %d\n", SQUARE(2 + 3), limit);
    printf("%d %d\n", marks[MAX_STUDENTS - 1], today);
    return 0;
}
Open in compiler

Line by line

  1. MAX_STUDENTS is replaced by 3 everywhere, before the compiler sees the code.
  2. SQUARE is safe, because of its brackets.
  3. enum day gives MON, TUE and WED the values 0, 1 and 2.
  4. SQUARE(2 + 3) becomes ((2 + 3) * (2 + 3)), which is 25.
  5. marks[MAX_STUDENTS - 1] is the last element, and today prints as the number 2.

Expected result

25 10
90 2

In the real world

Header files are full of macros and enums, such as EOF and the error codes in errno.h. Hardware registers are often given names with #define, and state machines in devices are usually built around an enum and a switch.

Common mistakes

  • A semicolon at the end of a #define, which then gets pasted into every use.
  • Macros without brackets, which give wrong answers inside larger expressions.
  • Using a macro with an argument that has a side effect, such as SQUARE(i++).
  • Forgetting that an enum is only a number, so %d prints 2 instead of a name.

Best practices

  • Replace every unexplained number with a name.
  • Prefer const and enum to #define where you can.
  • Write constants in capitals, so that they stand out.

Summary

const makes a typed value that cannot change. #define makes the preprocessor replace text, so macros need careful brackets. enum names a set of related whole numbers, and it works well with switch.

Quick check

1. With #define DOUBLE(x) x + x, what is 3 * DOUBLE(2)?
2. When does the preprocessor run?
3. enum { A, B = 5, C }; What is C?
4. Why is a function often better than a macro with arguments?

Key takeaways

  • Names make numbers meaningful and easy to change.
  • A macro is text replacement, so use plenty of brackets.
  • An enum names a small set of related choices.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.