Nearby lessons

108 of 124

C - enum

An enumeration gives names to a set of related integer constants. Learn the enum syntax, default and explicit values, how it beats a pile of #defines, and why an enum in C is not the airtight type it is in other languages.

Declaring an Enumeration

List the names inside braces. By default the first is 0 and each one after it is one more:

Example01
CCode Cell
1#include <stdio.h>
2 
3enum Colour { RED, GREEN, BLUE };
4 
5int main()
6{
7 enum Colour c = GREEN;
8 
9 printf("RED = %d\n", RED);
10 printf("GREEN = %d\n", GREEN);
11 printf("BLUE = %d\n", BLUE);
12 
13 printf("c = %d\n", c);
14 printf("sizeof(enum Colour) = %zu bytes\n", sizeof(enum Colour));
15 return 0;
16}
Output
RED   = 0
GREEN = 1
BLUE  = 2
c     = 1
sizeof(enum Colour) = 4 bytes

Explicit Values

Set any enumerator you like. Names that follow an explicit value continue counting from it:

Example02
CCode Cell
1#include <stdio.h>
2 
3enum Status { OK = 200, NOT_FOUND = 404, SERVER_ERROR = 500 };
4 
5enum Month { JAN = 1, FEB, MAR, APR }; /* 1, 2, 3, 4 */
6 
7enum Odd { A = 5, B, C = 10, D }; /* 5, 6, 10, 11 */
8 
9int main()
10{
11 printf("OK=%d NOT_FOUND=%d SERVER_ERROR=%d\n",
12 OK, NOT_FOUND, SERVER_ERROR);
13 printf("JAN=%d FEB=%d MAR=%d APR=%d\n", JAN, FEB, MAR, APR);
14 printf("A=%d B=%d C=%d D=%d\n", A, B, C, D);
15 return 0;
16}
Output
OK=200 NOT_FOUND=404 SERVER_ERROR=500
JAN=1 FEB=2 MAR=3 APR=4
A=5 B=6 C=10 D=11

Duplicate Values Are Legal

Two names may share a value. That is sometimes deliberate — an alias — but it also means you cannot map a value back to a single name:

Example03
CCode Cell
1#include <stdio.h>
2 
3enum Level {
4 LOW = 1,
5 MEDIUM = 2,
6 HIGH = 3,
7 NORMAL = 2, /* deliberate alias for MEDIUM */
8 MAX = 3 /* alias for HIGH */
9};
10 
11int main()
12{
13 printf("MEDIUM = %d, NORMAL = %d\n", MEDIUM, NORMAL);
14 printf("Equal: %s\n", MEDIUM == NORMAL ? "yes" : "no");
15 
16 /* You cannot tell which name produced a 2 */
17 enum Level l = 2;
18 printf("l = %d - MEDIUM or NORMAL? The value does not say.\n", l);
19 return 0;
20}
Output
MEDIUM = 2, NORMAL = 2
Equal: yes
l = 2 - MEDIUM or NORMAL? The value does not say.

Why Not Just #define?

In simple words: a set of #defines is a loose pile of numbers. An enum tells the compiler these values belong together, so it can auto-number them, keep them scoped, show the names in a debugger, and warn about a switch that forgets one.
enum#define
Auto-numberingYesYou maintain it by hand
Grouped as one typeYesNo
Visible in a debuggerYesNo — already replaced
Respects scopeYesNo
switch completeness warningPossibleNever
Example04
CCode Cell
1#include <stdio.h>
2 
3/* The old way - renumbering by hand, no grouping */
4#define D_SUNDAY 0
5#define D_MONDAY 1
6#define D_TUESDAY 2
7 
8/* The enum way - insert a day and the rest renumber themselves */
9enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY,
10 FRIDAY, SATURDAY, DAYS_IN_WEEK };
11 
12int main()
13{
14 enum Day today = WEDNESDAY;
15 
16 printf("today = %d\n", today);
17 printf("Days in a week = %d (the count comes free)\n", DAYS_IN_WEEK);
18 
19 printf("Defines: %d %d %d\n", D_SUNDAY, D_MONDAY, D_TUESDAY);
20 return 0;
21}
Output
today = 3
Days in a week = 7  (the count comes free)
Defines: 0 1 2

enum With switch

This is the pairing enum exists for. Every case reads as English, and with -Wall the compiler warns if you omit one:

Example05
CCode Cell
1#include <stdio.h>
2 
3enum Operation { ADD, SUBTRACT, MULTIPLY, DIVIDE };
4 
5float calculate(float a, float b, enum Operation op)
6{
7 switch (op)
8 {
9 case ADD: return a + b;
10 case SUBTRACT: return a - b;
11 case MULTIPLY: return a * b;
12 case DIVIDE: return (b != 0.0f) ? a / b : 0.0f;
13 }
14 return 0.0f;
15}
16 
17int main()
18{
19 printf("10 + 4 = %.1f\n", calculate(10, 4, ADD));
20 printf("10 - 4 = %.1f\n", calculate(10, 4, SUBTRACT));
21 printf("10 * 4 = %.1f\n", calculate(10, 4, MULTIPLY));
22 printf("10 / 4 = %.2f\n", calculate(10, 4, DIVIDE));
23 return 0;
24}
Output
10 + 4 = 14.0
10 - 4 = 6.0
10 * 4 = 40.0
10 / 4 = 2.50

Printing the Name, Not the Number

C has no built-in way to print an enumerator's name. The usual fix is a parallel array of strings — keep it in the same order as the enum:

Example06
CCode Cell
1#include <stdio.h>
2 
3enum Colour { RED, GREEN, BLUE, COLOUR_COUNT };
4 
5/* Same order as the enum above */
6const char *colourName[COLOUR_COUNT] = {"Red", "Green", "Blue"};
7 
8const char *nameOf(enum Colour c)
9{
10 if (c < 0 || c >= COLOUR_COUNT) return "Unknown";
11 return colourName[c];
12}
13 
14int main()
15{
16 enum Colour c;
17 
18 for (c = RED; c < COLOUR_COUNT; c++)
19 printf("%d = %s\n", c, nameOf(c));
20 
21 printf("Out of range: %s\n", nameOf((enum Colour) 99));
22 return 0;
23}
Output
0 = Red
1 = Green
2 = Blue
Out of range: Unknown

A Sentinel Count Is a Useful Habit

Adding a final ..._COUNT enumerator gives you the number of items automatically. Insert a new value anywhere above it and every loop and array size follows:

Example07
CCode Cell
1#include <stdio.h>
2 
3enum Priority { LOW, MEDIUM, HIGH, URGENT, PRIORITY_COUNT };
4 
5int main()
6{
7 int tickets[PRIORITY_COUNT] = {0}; /* sized automatically */
8 enum Priority p;
9 
10 tickets[LOW] = 12;
11 tickets[MEDIUM] = 7;
12 tickets[HIGH] = 3;
13 tickets[URGENT] = 1;
14 
15 for (p = LOW; p < PRIORITY_COUNT; p++)
16 printf("Priority %d : %d tickets\n", p, tickets[p]);
17 
18 printf("Total levels: %d\n", PRIORITY_COUNT);
19 return 0;
20}
Output
Priority 0 : 12 tickets
Priority 1 : 7 tickets
Priority 2 : 3 tickets
Priority 3 : 1 tickets
Total levels: 4

C Does Not Enforce the Range

An enum variable in C is an integer with a friendly name, not a restricted set. Assigning 99 to an enum Colour compiles without complaint, so any function that indexes an array with an enum value must still validate it. This is the single biggest difference from enums in stricter languages.
Example08
CCode Cell
1#include <stdio.h>
2 
3enum Colour { RED, GREEN, BLUE };
4 
5int main()
6{
7 enum Colour c;
8 
9 c = GREEN;
10 printf("c = %d (valid)\n", c);
11 
12 c = 99; /* no error, no warning by default */
13 printf("c = %d (nonsense, but accepted)\n", c);
14 
15 c = RED;
16 c++; /* arithmetic works too */
17 printf("c after ++ = %d\n", c);
18 
19 printf("\nAlways validate before using an enum as an index.\n");
20 return 0;
21}
Output
c = 1 (valid)
c = 99 (nonsense, but accepted)
c after ++ = 1

Always validate before using an enum as an index.

Bit Flags

Give each enumerator a distinct power of two and you can combine them with | and test them with &:

Example09
CCode Cell
1#include <stdio.h>
2 
3enum Permission {
4 P_NONE = 0,
5 P_READ = 1, /* 0001 */
6 P_WRITE = 2, /* 0010 */
7 P_EXECUTE = 4, /* 0100 */
8 P_DELETE = 8 /* 1000 */
9};
10 
11int main()
12{
13 int perms = P_READ | P_WRITE; /* 0011 = 3 */
14 
15 printf("perms = %d\n", perms);
16 printf("Read : %s\n", (perms & P_READ) ? "yes" : "no");
17 printf("Write : %s\n", (perms & P_WRITE) ? "yes" : "no");
18 printf("Execute : %s\n", (perms & P_EXECUTE) ? "yes" : "no");
19 
20 perms |= P_EXECUTE; /* grant */
21 perms &= ~P_WRITE; /* revoke */
22 printf("\nAfter changes: %d (read=%d write=%d exec=%d)\n", perms,
23 (perms & P_READ) != 0, (perms & P_WRITE) != 0,
24 (perms & P_EXECUTE) != 0);
25 return 0;
26}
Output
perms = 3
Read    : yes
Write   : yes
Execute : no

After changes: 5 (read=1 write=0 exec=1)

typedef, Anonymous Enums, and Scope

A typedef drops the enum keyword. An anonymous enum is a neat way to declare a group of constants with no variable type at all:

Example10
CCode Cell
1#include <stdio.h>
2 
3typedef enum { OFF, ON } Switch; /* named type */
4 
5enum { MAX_USERS = 100, MAX_NAME = 30 }; /* anonymous: just constants */
6 
7int main()
8{
9 Switch light = ON;
10 char names[MAX_USERS][MAX_NAME]; /* usable as array sizes */
11 
12 printf("light = %d\n", light);
13 printf("Capacity: %d users, %d chars each\n", MAX_USERS, MAX_NAME);
14 printf("Table size: %zu bytes\n", sizeof(names));
15 
16 /* Enumerators live in the enclosing scope, so names must be unique
17 across every enum in that scope. */
18 return 0;
19}
Output
light = 1
Capacity: 100 users, 30 chars each
Table size: 3000 bytes

Common Mistakes

  • Reusing an enumerator name — enumerators share the enclosing scope, so two enums cannot both define OK.
  • Expecting the first value to be 1 — it is 0 unless you say otherwise.
  • Trusting the range — validate before using an enum as an array index.
  • Expecting to print the nameprintf("%d", RED) prints 0; you need a name table.
  • Assuming a fixed size — the underlying type is implementation-defined.
  • Forgetting the semicolon after } — same rule as a structure.
  • Non-power-of-two values used as flags — the bits overlap and the tests give wrong answers.
Trainer's Note: the moment your code contains a magic number whose meaning you have to remember — a status, a mode, a state, a menu choice — that is an enum waiting to be written. It costs three lines and removes a whole class of "what was 2 again?" bugs.
📝 Key Takeaways
  • enum Colour { RED, GREEN, BLUE }; — RED is 0, GREEN 1, BLUE 2.
  • You may set values explicitly; unset names continue from the last one.
  • Enumerators are plain int constants known at compile time.
  • enum pairs naturally with switch and gives the compiler a chance to warn.
  • C does not stop you assigning any int to an enum variable.

🧠 Test Your Knowledge

4 Questions
Progress: 0 / 4