Code full of bare numbers is code nobody can read. if (state == 2) tells you nothing; if (state == STATE_RUNNING) tells you everything. An enum is C's way of giving a small, fixed set of integers proper names - and unlike a pile of #defines, they come grouped into one type the compiler knows about.
Defining an Enum
The declaration names a tag and lists the enumerators inside braces:
Two things to note straight away. First, numbering starts at 0 and increases by one, so RED is 0, GREEN is 1, BLUE is 2. Second, the enumerator names are visible everywhere in the enclosing scope - you write GREEN, not Color.GREEN. They live in the same namespace as your variables, which is why C code conventionally SHOUTS them or prefixes them by type (COLOR_RED) to avoid collisions.
Printing an enum uses %d, because an enumeration value is an integer value.
Explicit Values
Assign any enumerator an explicit constant, and the ones after it continue from there:
Values do not have to be unique or ordered - two enumerators may share a number, which is occasionally useful for aliases (COLOR_DEFAULT = COLOR_BLACK). They must be compile-time integer constants, so you cannot compute one from a variable.
A common idiom uses this to record the count:
enum Suit {
CLUBS, DIAMONDS, HEARTS, SPADES,
SUIT_COUNT /* automatically 4 - stays correct if you add a suit */
};
SUIT_COUNT is not a real suit; it is the number of them, maintained for free by the auto-numbering. Loops and array sizes can use it and never fall out of date.
typedef enum
As with structs, the full type name in C is enum Color, and a typedef drops the keyword:
Because enumerators are ordinary integers starting at 0, they double as array indices - dx[DIR_EAST] works without any conversion. That is the single most useful property of a zero-based enum.
Enums in a switch
Pairing an enum with a switch is where the type starts paying for itself. Compile with -Wall and the compiler will tell you when you forget a case.
Add a STATE_FAILED to the enum and GCC warns: enumeration value 'STATE_FAILED' not handled in switch. That warning is the closest thing C has to an exhaustiveness check, and it is a strong reason to write these switches without a default: case - a default silences the warning and lets new states slip through unhandled. Put the fallback after the switch instead, as describe does.
Converting an Enum to a String
C keeps no names at runtime; printf("%s", GREEN) cannot work because GREEN is simply 1. Two patterns solve it.
The switch version, which the compiler checks:
And the lookup table version, shorter but unchecked - if the array and the enum drift apart, nothing warns:
The designated array indices ([RED] = "RED") at least keep the names attached to their slots, so reordering the enum cannot silently shuffle the strings. The bounds check matters because nothing stops a caller passing a value outside the enum - see the next section.
Enums Are Not Airtight
An enum variable is an integer underneath, and C does not police the range:
Arithmetic on enumerators works, comparisons with plain ints work, and an out-of-range cast is accepted. So treat an enum as excellent documentation and a good compiler hint, not as a guarantee. Validate any value that arrives from outside your program - a file, an argument, a network message - before switching on it.
Size and Storage
In C17, every enumerator must fit in an int - enum Huge { BIG = 3000000000 }; is a constraint violation that a strict compiler (-pedantic-errors) rejects, even though gcc and clang accept it as an extension in their default mode. The enumerators themselves have type int, while the enum type's size is implementation-defined (in practice, int):
(C23 lifts the restriction: an enumerator may exceed int range and you can even pick the underlying type with enum E : unsigned long { ... }. Until your toolchain targets C23, keep enumerators inside int.)
Because the exact choice is implementation-defined, never write an enum directly into a binary file or a network packet and expect another machine to read it back. Convert to a fixed-width type such as uint8_t or uint32_t from <stdint.h> at the boundary.
Enum vs #define
Both give a name to a number. The enum is generally better:
enum | #define | |
|---|---|---|
| Handled by | the compiler | the preprocessor |
| Grouping | related values share a type | each constant is independent |
| Auto-numbering | yes | no, you number by hand |
| Visible in a debugger | often, by name | no, only the number |
switch exhaustiveness warning | yes | no |
| Scope | respects block scope | textual, until #undef |
Reach for #define when the constant is not an integer (a string, a float) or when it must be usable by the preprocessor itself, as in conditional compilation. For a set of related integer states, flags, or kinds, use an enum.
Naming Conventions
Enums collide easily because their names sit in the ordinary scope. The conventions that keep large C codebases sane:
- Prefix each enumerator with the type:
COLOR_RED,STATE_IDLE,HTTP_OK. UnprefixedREDin a graphics program will eventually meet someone else'sRED. - SHOUT_CASE the enumerators, matching how other constants look.
- Name the type in singular PascalCase or
snake_case_t:Color,State, orcolor_t, consistently across the project. - Add a
_COUNTsentinel last when you will loop or index with the enum.
Frequently Asked Questions
How do you define an enum in C?
enum Color { RED, GREEN, BLUE }; defines a type whose values are the three named constants. By default they number from 0, so RED is 0, GREEN is 1, and BLUE is 2. Declare a variable with enum Color c = GREEN;, or add a typedef so you can write just Color c.
Can you assign specific values to an enum in C?
Yes: enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };. You can set some and let others follow - any name without an explicit value continues from the one before it, so enum { A = 10, B, C }; gives B = 11 and C = 12.
How do you convert an enum to a string in C?
C has no built-in way; enumerator names do not exist at runtime. The usual approach is a small function with a switch returning a string literal per value, or an array of strings indexed by the enum. The switch version is safer because -Wall warns when a new enumerator has no case.
What is the size of an enum in C?
Implementation-defined, but in practice sizeof(int) - 4 bytes on most systems. The compiler picks an integer type able to hold every enumerator. Do not assume a specific size when laying out a binary file or network packet; use a fixed-width type from <stdint.h> there.