Enumerations

An enumeration gives names to a set of integer constants. The compiler checks that the names are unique, the debugger shows the names instead of the numbers, and a switch over the type documents every case. C does not stop a variable from holding a value outside the set, so the names are a contract rather than a fence.

Named integer constants

The first enumerator is zero and each following one is one more, unless a value is written explicitly. The enumerators are ordinary constants of type int, usable wherever an int is expected. A variable of the enum type holds one of those values.

#include <stdio.h> enum Color { RED, GREEN, BLUE }; int main(void) { enum Color favourite = GREEN; printf("RED %d\n", RED); printf("GREEN %d\n", GREEN); printf("BLUE %d\n", BLUE); printf("favourite %d\n", favourite); return 0; }

gcc -Wall -Wextra values.c -o values
./values

RED 0
GREEN 1
BLUE 2
favourite 1

The numbers are an implementation detail of the order; the program should compare names, not numbers. Writing enum Color { RED = 1, GREEN, BLUE }; starts the sequence at one, which matters when the values travel to a file or a protocol with fixed codes.

switch over an enum

A switch lists every enumerator, and with -Wswitch (part of -Wall) the compiler warns when one is missing. The function below returns a name for each value and keeps a final return for the value C allows but the enum does not document.

#include <stdio.h> enum Color { RED, GREEN, BLUE }; static const char *name(enum Color colour) { switch (colour) { case RED: return "red"; case GREEN: return "green"; case BLUE: return "blue"; } return "unknown"; } int main(void) { printf("%s\n", name(GREEN)); return 0; }

gcc -Wall -Wextra choose.c -o choose
./choose

green

Each case returns, so no break is needed; a switch that assigns instead of returning does need them, as lesson 13 showed. Adding a new colour to the enum now produces a warning in every switch that forgot it, which is the main reason to prefer this shape over a chain of ifs.

Values and flags

An enumerator may be any constant expression, so 1 << n turns the enum into a set of flags. The values are powers of two, a variable can combine several of them with |, and a test against one flag uses &. This is the bits lesson 11 with names.

#include <stdio.h> enum Permission { PERM_READ = 1 << 0, PERM_WRITE = 1 << 1, PERM_EXEC = 1 << 2 }; int main(void) { unsigned int permissions = PERM_READ | PERM_WRITE; printf("value: %u\n", permissions); printf("read: %d\n", (permissions & PERM_READ) != 0); printf("exec: %d\n", (permissions & PERM_EXEC) != 0); return 0; }

gcc -Wall -Wextra flags.c -o flags
./flags

value: 3
read: 1
exec: 0

The type of the variable is unsigned int, not the enum: combining flags produces a value the enum does not name, and storing it in the enum type would invite warnings from stricter compilers. The enumerators are the masks, the unsigned integer is the set.

typedef and the type name

Writing enum State at every declaration is verbose. A typedef gives the type a short name, and the enum tag becomes unnecessary. The size is the same as an int, because that is the type the enumerators have unless the values demand more.

#include <stdio.h> typedef enum { STATE_IDLE, STATE_RUNNING, STATE_STOPPED } State; static const char *label(State state) { return state == STATE_IDLE ? "idle" : state == STATE_RUNNING ? "running" : "stopped"; } int main(void) { State state = STATE_RUNNING; printf("%s\n", label(state)); printf("size: %zu\n", sizeof state); return 0; }

gcc -Wall -Wextra typed.c -o typed
./typed

running
size: 4

The typedef name is a normal type name now, usable in parameters and local variables without the enum keyword. The nested conditional is the short form; the switch version from the second section reads better once there are more than three states.

A duplicate and an unknown value

Enumerators share the namespace of ordinary identifiers, and two enumerators with the same name in one enum are a redefinition. The error names both lines, so the duplicate is easy to remove:

#include <stdio.h> enum Color { RED, GREEN, RED }; int main(void) { printf("%d\n", RED); return 0; }

gcc -Wall -Wextra duplicate.c -o duplicate

duplicate.c:6:5: error: redeclaration of enumerator 'RED'
6 | RED
| ^~~
duplicate.c:4:5: note: previous definition of 'RED' with type 'int'
4 | RED,
| ^~~

The second mistake is a value outside the enumeration. In C the enum type does not restrict the range, so a cast from an integer produces a value no case matches, and only the default branch catches it:

#include <stdio.h> enum Color { RED, GREEN, BLUE }; int main(void) { enum Color colour = (enum Color) 7; switch (colour) { case RED: case GREEN: case BLUE: printf("known\n"); break; default: printf("unknown value\n"); break; } return 0; }

gcc -Wall -Wextra outofrange.c -o outofrange
./outofrange

unknown value

A switch over an enum should keep a default for exactly this case, even when every enumerator is listed. When the value comes from a file or a network message, validate it before the cast: the enum tells the reader the intent, not the compiler the range.

Exercises

  1. Give the colors explicit values 1, 2 and 4 and use them as flags; print the combined value and test each bit.
  2. Add a new state to the typedef enum and compile a switch that handles only the old three. Which warning appears?
  3. Map a month enumeration to the number of days with a switch, and handle February separately.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What value does the first enumerator have when no value is written?

    Answer

    0

    Enumerators start at zero and increase by one, unless an explicit value changes the sequence.

  2. What does -Wswitch report?

    Answer

    An enumerator missing from a switch

    The warning names the enumerator that no case handles, which is why a switch over an enum is a useful checklist.

  3. How are an enum turned into a set of flags?

    Answer

    By assigning powers of two

    Each flag is one bit, so | combines them and & tests a single permission.

  4. Does an enum type stop a variable from holding another integer value?

    Answer

    No, a cast can store a value outside the set

    C treats the enum as an integer type with named constants, so validation is the program's job.

  5. What does this program print? #include <stdio.h> enum Color { RED, GREEN, BLUE }; int main(void) { enum Color favourite = GREEN; printf("RED %d\n", RED); printf("GREEN %d\n", GREEN); printf("BLUE %d\n", BLUE); printf("favourite %d\n", favourite); return 0; }

    Answer

    RED 0 / GREEN 1 / BLUE 2 / favourite 1

    The enumerators start at zero and increase by one, and the variable holds GREEN, which is 1.

  6. What does this program print? #include <stdio.h> enum Color { RED, GREEN, BLUE }; static const char *name(enum Color colour) { switch (colour) { case RED: return "red"; case GREEN: return "green"; case BLUE: return "blue"; } return "unknown"; } int main(void) { printf("%s\n", name(GREEN)); return 0; }

    Answer

    green

    The switch matches the GREEN case, which returns the string green.

  7. What does this program print? #include <stdio.h> enum Permission { PERM_READ = 1 << 0, PERM_WRITE = 1 << 1, PERM_EXEC = 1 << 2 }; int main(void) { unsigned int permissions = PERM_READ | PERM_WRITE; printf("value: %u\n", permissions); printf("read: %d\n", (permissions & PERM_READ) != 0); printf("exec: %d\n", (permissions & PERM_EXEC) != 0); return 0; }

    Answer

    value: 3 / read: 1 / exec: 0

    Read is 1 and write is 2, so combining them gives 3, and the read test is true while exec is false.

  8. What does this program print? #include <stdio.h> typedef enum { STATE_IDLE, STATE_RUNNING, STATE_STOPPED } State; int main(void) { State state = STATE_RUNNING; printf("%zu\n", sizeof state); return 0; }

    Answer

    4

    The enumerators have type int, so the enum type has the size of an int, which is four bytes here.

  9. This program is built with gcc -std=c17 -Wall -Wextra duplicate.c -o duplicate. What does GCC report? #include <stdio.h> enum Color { RED, GREEN, RED }; int main(void) { printf("%d\n", RED); return 0; }

    Answer

    error: redeclaration of enumerator 'RED'

    Enumerators share the ordinary identifier namespace, so the second definition is a redeclaration error.

  10. What does this program print? #include <stdio.h> enum Color { RED, GREEN, BLUE }; int main(void) { enum Color colour = (enum Color) 7; switch (colour) { case RED: case GREEN: case BLUE: printf("known\n"); break; default: printf("unknown value\n"); break; } return 0; }

    Answer

    unknown value

    No case matches 7, so the default branch catches it.

The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev

Banner image