> The main reason I keep coming back to assembly language with C, is that C cannot do anything that assembly language cannot do.
That's basically true. Except that an optimizer is allowed to transform things as long as the observable behavior remains the same. And undefined behavior means more than "just that one line is undefined" ( http://blog.llvm.org/2011/05/what-every-c-programmer-should-... , http://blog.llvm.org/2011/05/what-every-c-programmer-should-... , http://blog.llvm.org/2011/05/what-every-c-programmer-should-... ). These two facts conspire to make undefined behavior surprising to many programmers.
For instance -- using one of Lattner's examples -- on some architectures it's a little expensive to check if a loop variable had wrapped around. So the optimizer will omit the check for wraparound if it can prove wraparound is impossible. That is, if it can prove that n + 1 > n. But the Standard only requires wraparound for unsigned integer types. So the optimizer can also omit the check if it can only prove either n + 1 > n or n is a signed integer. In this case, a bounded loop turns into an infinite loop, but "undefined behavior" includes that kind of transformation.
More to the point, Linux had a severe security bug where they dereferenced a pointer, then checked if the pointer was NULL before returning the value ( https://lwn.net/Articles/342330/ ). The optimizer removed the check for NULL because if the pointer was valid when it was dereferenced, the check was unnecessary; and if the pointer was NULL when it was dereferenced, then dereferencing it was undefined behavior and "remove an NULL check" is a valid transformation in undefined behavior. Then moving that load to a different point in the function is also a valid transformation (as long as it doesn't affect observable behavior), and a few more transformations could make the NULL dereference do something completely different from what you expect.