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
- Give the colors explicit values 1, 2 and 4 and use them as flags; print the combined value and test each bit.
- Add a new state to the typedef enum and compile a switch that handles only the old three. Which warning appears?
- 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.
-
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.
-
What does
-Wswitchreport?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.
-
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. -
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.
-
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. -
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
GREENcase, which returns the stringgreen. -
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.
-
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 anint, which is four bytes here. -
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.
-
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