It dates to the first standardization of C in 1989. The "C as portable assembly" view ended when ANSI C got standardized, and K&R's 2nd edition was published.
I would argue it's the modern understanding of C standard is flawed.
Back in 89, many of those unspecified behavior were understood as implementation/hardware dependent, not undefined. Aliasing was the norm, `restrict` was actually a keyword.
Ascertaining the state of the mind of the C committee in 1989 is difficult, since only the documents from ~late 1996 are consistently available online (the earlier documents are probably sitting somewhere in a warehouse in Geneva, but they may as well not exist anymore).
But definitely by the time C99 came out, it is clear that optimize-assuming-UB-doesn't-happen was an endorsed viewpoint of the committee [1]. C99 also added restrict to the language (not C89 as you suggest), and restrict was the first standardized feature that was a pure UB-optimization hint [2].
It is important to remember that there isn't just one catch-all category of implementation-varying behavior. There is a difference between unspecified behavior, implementation-defined behavior, and undefined behavior. Undefined behavior has been understood, from its inception, as behavior that doesn't constrain the compiler, and often describes behavior that can't be meaningfully constrained (especially with regards to potentially-trapping operations).
[1] The C99 rationale gives an example of an optimization that compilers can perform that relies on assuming UB can't happen--reassociation of integer addition, on one's complement machines.
[2] The register keyword is I believe even in K&R C and would also be qualified as a compiler hint feature, but I note that it prohibits taking the address of the variable entirely, so it doesn't rely on UB. Whereas restrict has to rely on "if these two variables alias, it's UB" to allow the compiler to optimize assuming nonaliasing.
I haven't gotten to use C in industry, but I was taught that undefined behavior just means that it is defined by the running system and not the compiler. Is that not the general understanding? Maybe I was just taught that way because it was old timers teaching it.
If the language standard leaves some behavior undefined, other sources (e.g., POSIX, your ABI, your standard library docs, or your compiler docs) are free to define it. If they do, and you are willing to limit your program’s portability, you can use that behavior with confidence. But they also leave many behaviors undefined, and you can’t rely on those.
For implementation-defined behavior, the language standard lays out a menu of options and your implementation is required to pick one and document it. IMHO, many things in the C standard are undefined that ought to be implementation-defined. But unaligned pointer accesses would be hard to handle that way; at best you could make the compiler explicitly document whether or not it supports them on a given architecture.
Implementation Defined behavior means the standards authors provided a list of possible behaviors, and compiler authors must pick one and document which they picked.
Unspecified behavior is more what you're thinking of, though in that case the standard still provides a list of possibilities that compiler authors have to pick from, they just don't have to document it or always make the same choice for every program.
There's no allowed subset of behavior where compiler authors are free to pick whatever they want and document it (but must do so). IMO there should be, most "Undefined Behavior" could be specified and documented, even where that choice would be "the compiler assumes such situations are unreachable and optimizes based on that assumption" like much of current UB. At least it'd be explicit!
> Implementation Defined behavior means the standards authors provided a list of possible behaviors
The standard definitely does not require implementations to pick from a list of possible behaviors. All the standard requires is that the implementation document the behavior.
For example, the behavior on integer demotion is implementation-defined and there's no list of possible behaviors:
> When an integer is demoted to a signed integer with smaller size, or an unsigned integer is converted to its corresponding signed integer, if the value cannot be represented the result is implementation-defined.
> Unspecified behavior is more what you're thinking of, though in that case the standard still provides a list of possibilities that compiler authors have to pick from
That contradicts the standard's definition of unspecified behavior. For example, from the C89 draft (emphasis added) [0]:
> Unspecified behavior --- behavior, for a correct program construct and correct data, for which the Standard imposes no requirements.
The TL;DR is that compilers compile code based on assumptions that UB won't be invoked. This sometimes produces extremely surprising results which have nothing to do with the hardware/OS.
That’s why, while much of the linked blog is kind of off the mark (signs of someone knowing less than they think they know), the general conclusion, using aligned pointers is recommended, is one that I typically recommend to developers new to C or C++ anyway.
I’m alright with folks sticking to aligned pointer operations, largely for performance reasons. On some platforms, unaligned operations are really expensive.
There are some other reasons, but that's one of them.
Another is that you want to guarantee objects are stored aligned in memory because that gives you some free bits in pointers you can hide stuff in. (This has less hardware support than it should.)
My point here is that you can’t have “everything works as it does in the native assembly language” and “portable assembly” at the same time because if you rely on implementation defined or undefined behaviour then it’s not portable any more
That depends on what you mean by "portable". I think being able to use the same code across many platforms is enough to qualify. Being able to access raw machine behavior is part of the premise of portable assembly, not a disqualifier.
Back in 89, many of those unspecified behavior were understood as implementation/hardware dependent, not undefined. Aliasing was the norm, `restrict` was actually a keyword.
Modern C is neither safe nor low-level.