No, C89 is not the first place that notion was articulated, because C89 doesn't articlate that notion (and nor does any subsequent version of the Standard).
As far as the Standard is concerned, the term "Undefined Behavior" means nothing more nor less than that the behavior of the construct or corner case falls outside its jurisdiction. This implies that such case must not arise in any program whose behavior is to be fully specified by the Standard, i.e. strictly conforming C programs. Outside of the narrow realm of strictly conforming programs, however, the range of ways in which most compilers could behave in cases where the Standard imposes no requirements was expected to be constrained by a desire of compiler writers to sell compilers, and a consequent need to avoid behaving in a way customers and potential customers would view as unacceptable. While the Standard doesn't exclude the possibility that a compiler whose maintainers are hostile or indifferent to the needs of programmers targeting it might behave in completely unconstrained fashion in situations where the Stanard imposes no requirements, the "UB means anything can happen" notion would only be applicable to people who are stuck targeting such compilers.
A religion has formed among compiler writers who are sheltered from normal market forces, around the idea that the Standard only uses the phrase "Undefined Behavior" in situations which would never arise in any correct programs, interpreting the phrase "non-portable or erroneous" as "non-portable, and therefore erroneous", but that ignores not only the published Rationale documents for C89 and C99, but also every version of the C Standards Committee's charter to date, every single one of which has explicitly recognized the legitimacy of non-portable C code, and stated that the Standard it not meant to preclude the use of C as a form of "high-level assembly langauge".
I think the real shift came when proponents of clang and gcc were able to describe as "mainstream" behaviors which are unique to those two compilers and derivatives thereof, which for many tasks are effectively exempt from market competition by commercial compilers. If a programmer buys a $500 compiler that totally wipes the floor with clang and gcc, but wants to distribute code in contexts which would require that the recipients rebuild it, such distribution would require that the code be reworked to run on lower-quality freely distributable compilers, effectively negating any advantage the programmer should have been able to reap from buying a better compiler.
Incidentally, the C89 Standard does make a key distinction between "UB" and other behaviors that aren't fully specified by the Standard in the "as-if" rule and a corollary to it whose effects were intended to be minimal but which have become disastrous. Under the as-if rule, compilers may only perform optimizing transforms whose effects would not be observable under any defined program executions. A consequence of this is that if such a transform would observably affect the behavior of some sequence of actions, at least one of the actions involved must be characterized as invoking Undefined Behavior. This wouldn't be a problem if compilers interpreted the latitude they were given to be as narrow as required to perform specific useful tranforms. Unfortunately, aggressive compilers go beyond that, performing transforms that are seldom useful and are often best counter-productive.
An unfortunate aspect of this situation is that in many cases it may not be impossible to predict precisely how the optimal machine code that satisfies an application's requirements would behave in certain corner cases. Thus, allowing implementations to perform optimizing transforms in ways that might replace one acceptable corner-case behavior with another equally acceptable behavior would allow them to satisfy application requirements more efficiently than would otherwise be practical. If integer overflow were characterized as Implementation-Defined Behavior rather than UB, a compiler for a platform where integer overflows are trapped, given something like:
int test(int a, int b)
{
int temp=a*b;
if (f1())
f2(temp,a,b);
}
would be required to perform the multiplication (or at least determine whether it would overflow) unconditionally, before calling f1()
, even though code would likely not care about whether the computation of a*b
would overflow in cases where f1()
returned zero. Most applications whose requirements would be satisfied by the above code if integer overflow were characterized as Implementation-Defined Behavior would be satisfied equally well if not better if the code were transformed into:
int test(int a, int b)
{
if (f1())
f2(a*b,a,b);
}
even on platforms where the transformation would affect program behavior, but the Standard's abstraction model is incapable of allowing the above transformation without waiving jurisdiction over program behavior in case of integer overflow. While some compiler writers claim this allows more efficient code generation, it actually does the opposite by forcing programmers to write code in a manner that would block any optimizations transforms that could yield behaviors which would satisfy application requirements despite being inconsistent with the behavior of non-transformed code.