C Mastery / Undefined, Unspecified, and Implementation-Defined Behavior
Part 2 — The Object Model and Undefined Behavior

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

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

CategoryCompiler's obligationCan you rely on it?
DefinedMust do exactly what the standard saysYes
Implementation-definedMust do one of the allowed things *and document it*Only if you know the implementation
UnspecifiedMust do one of the allowed things, no documentation neededNo
UndefinedNothing at allNever

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

Undefined Behavior

rely on them.

Portability

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

it.

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.

References

behavior), Annex J.

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.ub.definedness07