liveBook · Manning

Undefined behavior

As explained in Modern C

Undefined behavior is a C execution condition for which the language standard imposes no requirements on the program's behavior.

Two similar loops

Compare `for (unsigned i = 1; i; ++i)` with `for (signed i = 1; i; ++i)`. The unsigned loop can reach zero and stop. In the signed loop, incrementing past the representable range has undefined behavior; a compiler may therefore treat zero as unreachable and turn the loop into an infinite one. section 5.7.5

   for (unsigned i = 1; i; ++i) do_something();
   for (  signed i = 1; i; ++i) do_something();


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":3},{\"line\":0,\"ch\":6}],[{\"line\":1,\"ch\":3},{\"line\":1,\"ch\":6}],[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":16}],[{\"line\":0,\"ch\":3},{\"line\":0,\"ch\":6}],[{\"line\":1,\"ch\":3},{\"line\":1,\"ch\":6}],[{\"line\":0,\"ch\":10},{\"line\":0,\"ch\":16}],[{\"line\":1,\"ch\":10},{\"line\":1,\"ch\":16}]]"}
!@%STYLE%@!

The side-by-side loops isolate the change in integer type that changes what the compiler may assume. section 5.7.5

Undefined behavior means C imposes no requirements on what happens when an operation reaches an undefined state. No assumption about the rest of that execution is safe. The compiler may also reason as though the state will never be reached. TAKEAWAY: A program need not crash or look strange to have undefined behavior; apparent success on one platform or with one set of compiler options is no guarantee. section 5.7.5

Other operations require the same care:

  • Dereferencing a null or indeterminate pointer with `*` has undefined behavior. A null pointer may crash promptly; an indeterminate pointer may instead modify an unintended object. section 11.1.1
  • Arguments to `printf` or `fprintf` must correspond exactly to the format specifiers. A wrong modifier can also cause the value to be interpreted with the wrong type. section 8.3.4
  • An aliasing trick that makes differently typed pointer parameters address the same object can make behavior undefined. Code must not rely on that trick to defeat the compiler's assumptions. section 12.3
  • A thread writing a non-atomic object while another thread simultaneously reads or writes it makes the execution undefined. Use atomic objects to avoid such conflicting shared accesses. section 18.1

TAKEAWAY: Avoid undefined behavior at the operation that could cause it. Neither a familiar bit pattern, a plausible output, nor a successful test run establishes that an otherwise undefined execution is safe. section 5.7.5

How Modern C covers it

How deep it goes
Explains consequences through signed overflow and gives operational rules for pointers, formatting, aliasing, and thread access without fully developing concurrency ordering.
Examples
Contrasts signed and unsigned incrementing loops and identifies invalid pointer dereferences, mismatched formatted output, and conflicting thread accesses.
What it assumes
Builds on signed and unsigned arithmetic, then defines pointer dereference in context before stating its invalid-pointer rule.
What you can do after
Evaluate/understand it by checking whether signed arithmetic, pointer use, formatted output, aliasing, or shared access can make execution undefined.
How it connects
Connects undefined behavior to signed overflow, the as-if rule, pointer dereference, format specifiers, aliasing, race conditions, and atomic objects.

Also explained in

Compare how 7 books explain undefined behavior →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Modern C. The section links point to the parts of the book it draws on.