5.1.25 · D5C Programming

Question bank — Enumerations

1,763 words8 min readBack to topic

Before you start, recall the anchor fact that dissolves half these traps: an enum's values are stored in some implementation-chosen integer type — one the compiler picks that can hold every enumerator's value (it may be int, or a wider/narrower signed or unsigned integer type; the standard does not guarantee int). The enumerators themselves, though, have type int when used in expressions. Either way, at runtime nothing exists but an integer; the names are compile-time labels. See Integer Types in C for what "an integer type" means — its signedness and size are platform-dependent, which matters for portability.

The number line below is the mental model: names are just sticky-notes on integer positions, and the counting rule walks rightward by one.

Figure — Enumerations

True or false — justify

printf("%d", RED) for enum { RED, GREEN } prints the word "RED".
False. C stores only the integer, so this prints 0. There is no name-lookup table like Python's repr; to print names you build your own const char* names[].
An enum introduces a brand-new type that the compiler refuses to mix with plain int.
False. In C an enum value freely converts to and from int, so enum Color c = 99; compiles with no error. The "type" is documentation, not enforcement — that safety exists in C++, not C.
Two enumerators in the same enum are allowed to hold the same integer value.
True. Only the names must be unique in scope; enum { WARN = 5, FATAL = 5 } is perfectly legal because two sticky-notes can label the same locker.
You must write Color.RED to reach an enumerator, like a struct field.
False. Enumerators are plain identifiers in the surrounding scope, so you write just RED. This is exactly why two enums in one scope cannot reuse a name — unlike a struct in C, whose members are reached as s.field and are scoped inside the struct.
Once you assign an explicit value, all later enumerators must also be explicit.
False. The "+1 over previous" rule never stops; after A = 5 an unassigned B becomes 6. Explicit values only restart the count, they don't disable it.
An enum constant can be used as a case label in a switch statement.
True. Because enumerators are compile-time integer constants, they satisfy switch's requirement that each case be a constant expression — which is exactly why enums and switch pair so naturally.
Enum values are stored as strings so they take more memory than ints.
False. They are stored in an implementation-chosen integer type, never strings. The names cost nothing at runtime — they vanish after compilation.
enum Weekday { MON, TUE }; uses the same memory whether you write MON or 0.
True. MON is 0 after compilation; they are byte-for-byte identical at runtime. The name only changes what a human reading the source sees.
The field in struct S { int x; } and the RED in enum { RED } live in the same kind of scope.
False. The struct's x is a member, reached only via s.x; the enum's RED is a top-level identifier reachable bare. That difference is the whole reason two enums can't share a name but two structs can both have a member x.

Spot the error

enum Color { RED, GREEN, BLUE } then if (c == "RED") — what's wrong?
c is an integer and "RED" is a string literal (a char* address), so you're comparing an int against a pointer — meaningless, and modern compilers warn ("comparison between pointer and integer"), which becomes a hard error under -Werror. You meant if (c == RED), comparing the int against 0.
enum A { X }; enum B { X }; in the same file — why does this fail?
Both declare an identifier named X in the surrounding scope, and a scope cannot hold two things with the same name. Enumerators are not namespaced by their enum, so the second X collides.
switch(c) { case RED: puts("stop"); case GREEN: puts("go"); } — bug?
The missing break after case RED causes fall-through: matching RED prints both "stop" and "go". Every intentional case in a switch normally needs its own break.
enum E { A = 1, B, A = 4 }; — legal?
Illegal — not because of the value 4, but because the name A is declared twice. Duplicate values are fine; duplicate names are the actual violation.
typedef enum { RED, GREEN } Color; enum Color c; — what's wrong?
The enum has no tag (nothing after enum), so enum Color names no type at all — that's a hard compile error, not merely redundant. The typedef already made Color the type name, so you must write Color c; with no enum keyword.
const char* name = RED; where RED is an enumerator — what breaks?
RED is the integer 0, so this assigns the null pointer to name (integer 0 converting to a pointer). You wanted a lookup array names[RED], not RED itself.

Why questions

Why does the first un-initialized enumerator get 0 and not 1?
Because the C standard fixes the first enumerator without an initializer at 0, matching C's zero-based habit (like array indices). Everything after follows from the single recurrence "previous + 1".
Why are duplicate enum values allowed but duplicate names forbidden?
A value is just a number the machine stores, and nothing breaks if two labels point to the same number. A name is an identifier the compiler must resolve to one thing, so two identical names would be ambiguous.
Why prefer an enum over a #define for a set of related constants?
Enums are grouped as one logical type, obey scope, and appear in the debugger and switch warnings; #define is raw text substitution with none of that structure. The enum documents "these belong together."
Why does the trailing-COUNT trick automatically equal the number of items?
Because counting starts at 0, an item at position n has value n, so the enumerator placed after the last real item lands on the count exactly. Add another item before it and the recurrence bumps COUNT up for free — useful for sizing Arrays in C.
Why can't C tell you if you assigned a nonsense value like 99 to an enum Color?
Because C treats the enum as an ordinary integer, and 99 is a valid integer. C deliberately trades type-safety for simplicity and speed here — the burden of "is this a real color?" is on you.
Why does printf("%d", GREEN) work at all if enums are "a type"?
Because an enumerator has type int in expressions, and %d expects exactly an int — the match is trivial and lossless. If enums were truly strict types, this would need a cast; in C it does not.

Edge cases

enum { A = -3, B, C }; — what are B and C?
B = -2 and C = -1. Negative explicit values are legal, and the "+1 over previous" rule still applies, walking upward from -3.
enum { A = 5, B = 5, C }; — what is C?
C = 6. C is previous+1 from B's value 5, and the fact that A and B collide on 5 is irrelevant to the counting.
enum Empty { }; — is an enum with no enumerators allowed?
It is not valid C — an enum must list at least one enumerator, because there is nothing to name or count otherwise. (Some compilers permit it as an extension; don't rely on it.)
enum { A = 2147483647, B }; where the chosen type is 32-bit signed — problem?
B would be A + 1, which overflows a signed 32-bit integer, and signed integer overflow in C is strictly undefined behavior (not merely implementation-defined). The recurrence is blind to limits, so you must keep values within the type's range — see Integer Types in C.
enum Color c; declared but never assigned — what value does it hold?
If it's a local variable, it is uninitialized garbage exactly like any uninitialized integer variable; it is not automatically RED. Enums get no special zero-init beyond the normal rules for their storage duration.
Can an enumerator's explicit value be another enumerator, like { A = 1, B = A }?
Yes — enumerators are constant expressions, so B = A sets B to 1, and a later unassigned C becomes 2. You can build values out of earlier ones as long as they stay compile-time constants.

Recall One-line summary to lock in

Enums are self-documenting integer labels: names must be unique, values need not be; the enumerators are int-typed constants stored in an implementation-chosen integer type; and C enforces almost no type safety — treat every trap above as a consequence of those facts. The trap-killer sentence ::: "It's an integer constant with a nice name, counted from 0 by +1, restartable, and not type-checked."