A Set of Names, Numbered For You

Programs are full of small fixed sets: the days of the week, the states a connection can be in, the options on a menu. Written as bare numbers they turn code into a puzzle, because if (state == 2) says nothing about what 2 means. The last lesson gave you #define for naming a single value, and you could certainly write seven of them for the seven days. But nothing then records that those seven names belong together, and you get to do the numbering by hand.

An enumeration solves exactly this shape of problem: it declares a type whose values are a named set, and numbers them for you.

Declaring an Enumeration

The declaration lists the names in braces:

enum weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY };

Two separate things now exist. The first is a type, whose name is enum weekday — both words, always. The bare word weekday is not a type in C, and writing weekday today; is an error rather than a shortcut. (If you have met C++, this is one of the places the two languages genuinely differ, and later you will meet typedef, which is how C programmers buy themselves the shorter name.) The second is five enumeration constants, MONDAY through FRIDAY, which are ordinary integer constants usable anywhere a number is.

Declaring a variable of the type looks like any other declaration:

enum weekday today = WEDNESDAY;

The Numbering Rule

Left to itself, the compiler numbers the constants from zero, in the order you wrote them, each one more than the last:

MONDAY = 0
FRIDAY = 4
today = 2

Zero-based numbering is the detail exam questions are built on. MONDAY is 0, not 1, and the fifth name is 4. Notice also that %d prints an enumeration value, because what is stored is an integer.

Setting Values Yourself

Any constant in the list may be given an explicit value with =, and counting resumes from there:

STATUS_OK = 1
STATUS_WARNING = 2
STATUS_ERROR = 10
STATUS_FATAL = 11

STATUS_WARNING follows the 1 it was given and becomes 2; STATUS_FATAL follows the 10 and becomes 11. The rule is simply "one more than the previous constant, whatever set that one". This is how you match numbers a protocol or a file format has already chosen for you, and it is why enum status { A = 1, B = 1 }; is legal: nothing requires the values to be distinct.

Declaring the Type and Variables Together

The list and a variable declaration can be combined into one statement, which is common in older code and in textbooks:

enum weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY } today, tomorrow;

That declares the type and the two variables today and tomorrow at once. If you never need the type name again, you may leave the tag out entirely:

enum { SCREEN_WIDTH = 80, SCREEN_HEIGHT = 25 };

This declares no variables and no reusable type name. What it does declare is two integer constants, and that turns out to be one of the most useful things an enumeration can do in C.

An Enum Constant Is Just an int

Here is the part that separates knowing the syntax from understanding it. In C, an enumeration constant has type int, and an enumeration variable has whatever integer type the implementation finds convenient. There is no separate, protected, scoped enumeration type in this language; that is C++'s enum class, which does not exist here. The practical consequence is that C does not check the value:

today = 4
nonsense = 99
sizeof(enum weekday) = 4

99 is not a weekday, and the compiler said nothing at all, warnings and pedantic mode included. So an enumeration is documentation the compiler helps you write, not a guarantee it enforces. Treat a value arriving from outside your program as an ordinary integer that needs checking, and when you eventually write a switch over an enumeration, give it a default branch for the value that should not be possible.

Two smaller facts belong here. Enumeration constants are constants, not variables: they have no address and cannot be assigned to, so MONDAY = 7; is an error. And sizeof reports 4 above only because this implementation chose int; the standard lets it choose any integer type that fits the values.

The Compile-Time Constant C Otherwise Lacks

The previous lesson left a promise open. A const variable in C is still a variable, so the contexts that demand a genuine constant expression will not take one, and array sizes are the case you meet first. #define works there because the substitution happens before the compiler sees the array. An enumeration constant also works there, and it is what most C programmers reach for:

enum { MAX_STUDENTS = 50 };

That is a real constant expression, typed int, produced by the compiler rather than by textual substitution, and visible under its own name in a debugger. Arrays arrive in chapter 7, and this is the form to use when you need to size one; the preprocessor's traps, which chapter 14 catalogues, are all avoided because no preprocessor is involved.

The rule of thumb for integers is therefore short: a set of related values, or a single integer constant, wants an enum. Reach for #define when the value is not an integer, as with #define PI 3.14159, since enumerations hold integers only.

Naming, and One Syntax Trap

Enumeration constants do not get a namespace of their own; they sit in the scope that encloses the declaration, exactly as a variable would. Two enumerations in one file that both offer RED will collide. The convention that avoids this is to prefix every constant with the set's name, which is why the examples above say STATUS_OK rather than OK, in the upper-case style C uses for constants generally.

The trap is a comma. A list may not end with one in this dialect:

enum color { COLOR_RED, COLOR_GREEN, };

A trailing comma was made legal in C99 and is harmless in modern code, but under the C89 rules this course compiles with, the compiler rejects it:

error: comma at end of enumerator list

If you meet that message, delete the comma before the closing brace.

Key Takeaways

  • An enumeration declares a type whose values are a named set: enum weekday { MONDAY, ... }; declares the type enum weekday, both words, plus the constants.
  • Constants number from zero upward by default, in declaration order.
  • An explicit = value sets one constant and the next continues from it; values need not be distinct.
  • The type and variables may be declared together, and a tagless enum { NAME = value }; declares constants and nothing else.
  • Enumeration constants have type int and C checks nothing: any integer may be stored in an enumeration variable, so a switch over one still needs a default.
  • enum { MAX_STUDENTS = 50 }; is the idiomatic C integer constant for places that demand a constant expression, such as array sizes, where a const variable is not accepted.
  • Prefix constants with the set's name to avoid collisions; a trailing comma in the list is a C99 relaxation that C89 rejects.