You started the discussion by saying that we can understand what the committee meant by simply reading the text in the standard that they wrote. Remember? Why then does it matter if it's non-normative text (aka. informative)? If it's non-normative it's there to explain what they were thinking.

I double-checked my copy of the C89 standard (draft), and as far as I can tell the text is normative. Non-normative text includes footnotes and appendices and editor's notes in square brackets, but I didn't see any of those involved. Sometimes there's a section that states 'the following text is non-normative' but I didn't see any of that either. Why do you think it's non-normative?

If that text is non-normative, where is the normative definition of undefined behavior?

Regardless, I think I understand what's happened here. If I may? You've read the text and you've repeatedly commented that we have to discard the "it allows you to do anything" interpretation because that interpretation is nonsense.

I'll stipulate that when reading English, we regularly have to discard nonsense interpretations. "Out the window, the mountains looked over a beautiful lake", we discard the interpretation where the mountains have eyes and are looking out a window. This is a normal part of reading English.

I posit that you believe that the "anything can happen" interpretation is impossible so strongly that no possible wording would ever lead you to the interpretation intended by the committee.

So instead, how about I explain why the committee chose to define UB this way? How it isn't nonsense?

It's so that a C compiler could use the plain "add" instruction for an addition in C across all the crazy CPU designs. Here, let me make a simple example, this isn't a real CPU. Suppose the CPU has a status register which contains "signed overflow" as one of its bits. This bit is set or cleared when you do an ALU operation, including ADD. The same status register is reused when doing a memory operation, but that bit is reused to indicate whether you're going to the first or second bank of memory. The CPU authors think that this is great, if you're doing pointer math and you add a pointer and an integer then you transparently overflow from the first bank of memory into the second and it looks like a contiguous address space! The system integrator (or, motherboard designer, roughly) decides to use bank selection for a different purpose. There's no way the first bank of memory would ever be completely full (nobody buys or sells that much RAM) so they put the RAM on bank 0 and the I/O ports on bank 1. Their system has memory-mapped I/O! So far, everybody's done something that seems sensible to them. Third, the C programmer writes "*p = x + y;". What happens if x + y are signed ints and the addition overflows? The signed overflow bit gets set, then the STORE instruction accesses the I/O ports instead of the memory!

Is the C compiler buggy for not inserting an extra instruction that clears the sign bit of the register? The committee intentionally decided that no, this is how signed integers should work and if you want potentially slower integers with guaranteed semantics that you should use unsigned integers instead. I think that attaching well-definedness to signed/unsigned was a bad move, but this is what they did. (And it does what you've said you want in other comments: the + in C becomes whatever the machine ADD instruction does!)

The C committee invented undefined behavior as a way to ensure that the compiler really could ignore the situation. (FWIW, real CPU ISAs back then had all kinds of interesting ideas. We hadn't yet agreed that bytes are 8 bits. Or that we should use 2's complement. Some designers looked at division by zero as an invalid operations and thought that this was an excellent feature that should be brought over to other operations like add and mul, hence "trap values" in the C standard.)

C and Unix were a commercial success (CPU firms could skip writing their own OS every time), and starting then, CPU designers made ISAs where C could be easily lowered to efficient assembly on their machines. This notion that there was an efficient lowering from C to the CPU, at the time C was standardised, is an anachronism. C created UB to handle then-contemporary CPUs, then later CPUs created ISAs that matched C syntax. If you don't work in compilers or assembly or CPU design, this might be surprising, but if you aren't intentional about making your ISA work well in C, it's easy to accidentally make one which doesn't. Intel MMX famously couldn't be targeted from compilers because the compilers don't have sufficient information to solve where to put the necessary EMMS instructions. Oops!

Decades down the road, CPU designs evolved and started creating new patterns that don't match C well -- and the C language didn't evolve with them. What expression is PSHUFB in C? Or VPMASKMOVD? The CPU firms knew enough to make sure that the compilers could support their new instructions, so they added these as CPU-specific extensions. If you wanted them, you had to write non-portable code that only worked when compiling to target their CPU and not others.

The compiler engineers believe in C being a portable language. If you write code using SSE or AVX and compile it to ARM with clang, it will compile and port the SSE builtins to ARM Neon vector extensions. Doing this required the compilers to be a whole lot smarter about how the code works, and is a large part of the source of modern complaints about compilers "exploiting" undefined behaviour. I counted 23 ADD instructions in contemporary x86-64, assuming you include fused instructions (LEA) and exclude things like OR (saturating add without carry between bits) and XOR (addition with 1-bit vector lanes, lol).

Finally, C defines an abstract machine. In this context, machine is a "term of art" in computer science, popular types of machines include cellular automata (the machine side of the 'regular expression' language), push-down automata and Turing machines. If you've seen those before, you may know that they're usually pictured as a directed graph, with states drawn as nodes and state transitions as directed edges, the edges labelled with the circumstance under which this edge is taken. Now, C's machine and the three I listed have a key difference, those three are all decision machines, meaning they exist to either accept or reject an input string. The C machine is a functional machine, it describes a function that transforms and input to an output. (In this treatment, you may picture side-effects as being part of the output.) The C standard defines how such C abstract machines are written down (the C syntax) and what semantics they have: states, and state transitions. The question is what happens when you are in a state and receive an input for which the standard does not define any particular state transition? In a cellular automata or PDA or Turing machine you have a single state named "error" (or "reject") at which point you reject the input string as not being a member of the set that the machine is deciding (aka., your input string fails to match the regex, and we're done). In a functional machine, we don't traditionally have such a state. By defining UB in the way they did, the C standard is stating that when no state transition is specified, it may go anywhere, including to states that aren't required to exist and on which the standard places no requirements.