Undefined, Unspecified, and Implementation-Defined Behavior
This chapter precisely distinguishes the three categories of behavior that the C standard does not fully define, and explains why the distinction matters for portability, security, and optimization.
Why This Matters
Most C bugs and portability problems come from conflating these categories. "Undefined behavior" means the compiler owes you nothing; "implementation- defined" means the compiler must document a choice; "unspecified" means the compiler picks freely but validly. Knowing which is which tells you what you can and cannot rely on.
Prerequisites
c.object.1— the object model.
Core Concept
Undefined behavior (UB)
Undefined behavior is behavior for which the standard imposes no requirements. Anything can happen: the program may crash, produce wrong output, or appear to work. The compiler may assume UB never occurs and optimize accordingly.
int x = INT_MAX + 1; /* signed overflow: UB */
Implementation-defined behavior
Implementation-defined behavior is behavior that can vary between implementations, but each implementation must document what it does.
int x = -1 >> 1; /* implementation-defined for negative signed right shift */
The size of int, the signedness of char, and struct padding are all implementation-defined.
Unspecified behavior
Unspecified behavior is behavior that can vary between implementations (or even between runs of the same program), but the set of possible behaviors is bounded by the standard and need not be documented.
int x = f() + g(); /* order of evaluation of f() and g() is unspecified */
The Difference, Summarized
| Category | Compiler's obligation | Can you rely on it? |
|---|---|---|
| Defined | Must do exactly what the standard says | Yes |
| Implementation-defined | Must do one of the allowed things *and document it* | Only if you know the implementation |
| Unspecified | Must do one of the allowed things, no documentation needed | No |
| Undefined | Nothing at all | Never |
Examples
Defined
unsigned int x = 1u + 1u; /* always 2, wraps if it overflows */
Implementation-defined
printf("%zu\n", sizeof(int)); /* implementation-defined size */
Unspecified
int i = 0;
int j = f(i++) + g(i++); /* order unspecified; may be UB too */
Undefined
int *p = NULL;
int x = *p; /* null dereference: UB */
How It Works
The C standard defines an abstract machine and a set of constraints. Where the standard is silent, behavior falls into one of the three non-defined categories. Compilers are free to exploit UB for optimization because they can assume it never happens; this is covered in c.opt.2.
Variations
Annex J
The C standard's Annex J lists many (but not all) examples of undefined, unspecified, and implementation-defined behavior. It is a useful checklist, not a complete enumeration.
Sanitizers detect some UB
UBSan (c.debug.ubsan) instruments code to detect many categories of UB at run time. It cannot catch everything, but it is an essential tool.
Common Mistakes
- Treating implementation-defined behavior as portable.
- Treating unspecified behavior as UB (it is not necessarily UB).
- Treating UB as "works on my machine" (it may change with optimization).
- Assuming Annex J is exhaustive.
Undefined Behavior
- This chapter is *about* UB. The examples themselves illustrate it; do not
rely on them.
Portability
- The categories are defined by the standard and apply everywhere.
- The *specific* choices (e.g., size of
int) are implementation-defined and
vary.
Under the Hood
Compilers exploit UB aggressively. For example, if a program contains INT_MAX + 1, the compiler may assume that branch is unreachable and remove code, or fold the expression to an arbitrary value. This is why UB is not "whatever the hardware does."
Practical Usage
- Write to the defined subset of C.
- When you must use implementation-defined behavior, isolate it and document
it.
- Avoid unspecified evaluation order by sequencing operations explicitly.
- Run sanitizers to find UB.
Exercises
1. Classify each of these as defined, implementation-defined, unspecified, or UB: sizeof(int), -1 >> 1, f() + g(), *NULL, 1u + 1u. 2. Look up Annex J in the C standard and find five examples of each category. 3. Write a program with a signed overflow and observe UBSan's diagnostic.
Deep Challenge
For each category, give a concrete example where *incorrectly* classifying it leads to a real bug. Explain how a compiler engineer and a security engineer would each view UB differently.
Related Concepts
c.ub.catalog— the catalog of common UB.c.opt.2— UB-based optimization.c.debug.ubsan— sanitizers.
References
- ISO/IEC 9899:2018 §3.4 (undefined/unspecified/implementation-defined
behavior), Annex J.
Verification
- Definitions of the three categories are standard.
VERIFIED - Signed overflow is UB.
VERIFIED sizeof(int)is implementation-defined.VERIFIED- Function argument evaluation order is unspecified.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Undefined behavior
- [ ] Implementation-defined behavior
- [ ] Unspecified behavior
- [ ] Category distinctions
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.ub.definedness | 0 | 7 |