What are you talking about? UB was coined only in the first C standard, in 1989. Prior to that there was no "If you do this, anything can happen". It was "If you do this, that will happen".
Pre 1989, when C did not have a standard, was the behavior unspecified or undefined? That is, of course, a trick question. Because in this context the very definitions of the words come from the standard itself.
Before a language gets a specification, is the de facto specification the five words "you know what I mean"?
The very definition of "UB" in C later became "[…] this document imposes no requirements". Is that not the same thing as "there is to specification (yet)"?
It sounds very zen, but "a non existing specification imposes no requirements".
But I don't think it's meaningful to argue the semantic difference before the (in-context) existence of the words "undefined" vs "unspecified".
> Prior to that there was no "If you do this, anything can happen".
Of course it was. You relied on "common sense".
> It was "If you do this, that will happen".
Haha, of course it wasn't. Before a specification there is neither a definition of "this" nor "that".
Unless you mean ye olde "the compiler implementation is the specification". In which case we'll get dragged into "what even is a language" and "what is the sound of one hand clapping?".
Or, alternatively, it's as true then as it is today. If you go by "GCC x.y.z on platform Z kernel Y, (etc…) is the specification" then there is no UB.
> UB was coined only in the first C standard, in 1989. Prior to that there was no "If you do this, anything can happen".
I.e., the context is, before UB existed as a concept, how would these things be categorized. And I was trying to offer the correction that, before UB existed, it wasn't "all behavior is defined" but rather many behaviors depend on your particular local environment. While that may technically be implementation defined, the current standard requires that implementation defined be documented, and UB-like edge cases were most definitely not documented anywhere consistently in the old days!
Consider, for example, an implementation defined function f() -- which can also diverge/crash horribly, etc.
If I write
if p {
print("p is true")
} else {
g()
}
if p {
f()
}
Then either we:
- print p is true and execute f
- do nothingThis is true regardless of if f immediately crashes the computer, nasal demons, whatever -- that's implementation defined.
UB means f may never happen.
And that means the compiler may optimize this to just:
g()
Notice the difference here -- the print never happens!, and g always happens.You can see why this is concerning when you write code like
if dry_run {
print("would run rm -rf /")
} else {
run("rm -rf /")
}
if dry_run {
// oops: some_debug_string is NULL and will segfault!
print(some_debug_string);
}