Enums and Typedef
This chapter covers enum (named integer constants) and typedef (type aliases). Typedef is the tool for reading and writing complex declarations, especially function pointers.
Why This Matters
Enums give meaning to integer values and are the backbone of state machines and flags. Typedefs make complex types readable and maintainable — and, used well, are the difference between code you can read and code you cannot.
Prerequisites
c.core.3— integer types.
Core Concept
Enums
An enum declares a set of named integer constants:
enum Color { RED, GREEN, BLUE };
RED, GREEN, and BLUE are constants of type int with values 0, 1, 2 by default. You can assign explicit values:
enum Color { RED = 1, GREEN = 5, BLUE = 9 };
If a value is omitted, it continues from the previous value + 1.
The enum *type* (enum Color) is compatible with some implementation-defined integer type (usually int if all values fit). The constants themselves are int.
Flags with enums
Use power-of-two values for bit flags:
enum Perm {
PERM_READ = 1 << 0,
PERM_WRITE = 1 << 1,
PERM_EXEC = 1 << 2,
};
Combine with |, test with &.
Typedef
typedef creates an alias for a type:
typedef unsigned long size_t;
typedef struct Node Node;
typedef int (*cmp_fn)(const void *, const void *);
After typedef, the alias can be used anywhere the original type could.
Syntax
enum Name { CONST1, CONST2 = value, CONST3 };
typedef existing_type alias_name;
Examples
Basic enum
#include <stdio.h>
enum Status { STATUS_OK, STATUS_ERROR, STATUS_PENDING };
int main(void)
{
enum Status s = STATUS_OK;
printf("%d\n", s);
return 0;
}
Expected output: 0.
Function-pointer typedef
typedef int (*binary_op)(int, int);
int add(int a, int b) { return a + b; }
int apply(binary_op op, int x, int y)
{
return op(x, y);
}
int main(void)
{
return apply(add, 2, 3); /* returns 5 */
}
Without the typedef, binary_op would be written inline as int (*)(int, int), which is hard to read and easy to get wrong.
Struct typedef
typedef struct Node {
int value;
struct Node *next;
} Node;
Node *head; /* clearer than struct Node *head */
How It Works
Enums are a compile-time mapping from names to integer constants. Typedefs are compile-time aliases; they produce no code and reserve no storage. Both are resolved entirely by the compiler.
Variations
Typedef vs. #define
typedef is handled by the compiler and understands types; #define is a textual replacement done by the preprocessor. Prefer typedef for types and enum/const for constants over macros.
Pointer typedefs and const
typedef int *int_ptr;
const int_ptr p; /* p is a const pointer to int (int *const), NOT int const * */
This is a common trap: const applies to the whole typedef'd type, so const int_ptr is int *const, not const int *.
Common Mistakes
- Assuming enum constants are a distinct type that cannot be mixed with
int
(in C, they are int constants; C++ differs).
- Using
#definewheretypedefis clearer. - Misreading typedefs with pointers and
const. - Forgetting the semicolon after a struct/enum/union definition or a typedef.
Undefined Behavior
- Assigning an out-of-range value to an enum *variable* can be
implementation-defined (the enum type is an integer type; the set of values it can represent is implementation-defined).
- Enums themselves are not UB-prone; the usual integer overflow rules apply
when used in arithmetic.
Portability
- Enum constants are
intin C; the underlying type of the enum *type* is
implementation-defined but must be able to represent all enumerators.
- C23 adds the ability to specify the underlying type (`enum Color : unsigned
char`), but this is C23-only.
Under the Hood
Enums are just integers in generated code; the names exist only at compile time. Typedefs are erased before code generation; they exist only in the compiler's type system.
Practical Usage
- Use enums instead of magic numbers for states, status codes, and flags.
- Use typedefs to name function-pointer types and complex struct/pointer types.
- Use typedefs for portability (e.g., typedefs in
<stdint.h>).
Exercises
1. Define an enum for HTTP-like status codes with explicit values and print them. 2. Define a bit-flag enum and write set/clear/test functions. 3. Write a function-pointer typedef for a comparator and use it with a simple sort. 4. Demonstrate the const int_ptr vs const int * distinction.
Deep Challenge
Build a small table-driven state machine using an enum for states and a function-pointer typedef for transitions. Explain why enums + function pointers together form a clean, extensible design.
Related Concepts
c.func.fnptr— function pointers.c.core.32— callbacks.c.pp.object-macro— #define vs. enum/typedef.
References
- ISO/IEC 9899:2018 §6.7.2.2 (enumeration specifiers), §6.7.8 (typedef).
Verification
- Enum constants are
intin C.VERIFIED - Enum underlying type is implementation-defined.
VERIFIED typedefis compile-time only.VERIFIEDconst int_ptrisint *const.VERIFIED- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] enum declaration and values
- [ ] Explicit enum values and flags
- [ ] Basic typedef
- [ ] Pointer typedef
- [ ] Function-pointer typedef
- [ ] typedef + const trap
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.enum.decl | 0 | 5 |
| c.typedef.basic | 0 | 5 |
| c.typedef.ptr | 0 | 5 |
| c.typedef.fnptr | 0 | 5 |