Hacker News new | ask | show | jobs
by saagarjha 27 days ago
What should the behavior above be defined to do?
5 comments

“Implementation defined behaviour”: compiler author chooses, and documents the choice.

A lot of UB should be implementation defined behaviour instead; this would much better match programmers’ intuitions as they reason about their code - you can even see it in the comments of this post: it’s always things like “this hardware supports / doesn’t support unaligned accesses”, it’s never nasal demons.

I told someone at a conference that UB actually means "implementation-defined, no documentation required". He started to refute me and then stopped.
That isn't true, for UB the compiler is allowed to assume the UB can never happen. For example if you dereference a pointer and only after check if it is NULL, the compiler can remove the NULL check, since it is clearly impossible (nevermind that you might be on a microcontroller where NULL is a valid address).

The fallout of this are quite large! If behaviour is implementation defined the compiler has to stick to one consistent behaviour. No such need for UB, you can get different behaviour bu changing unrelated code, by changing between debug and release or just because of what garbage happened to be on the stack.

Since the compiler is allowed to assume the UB doesn't happen it will also sometimes look like the compiler miscompiled your code elsewhere, but what actually happened was some inlining followed by extrapolating "this can never happen".

UB is often surprising: I have seen unaligned loads crash on x86 due to it bring UB in C (even though x86 is generally fine with it). But once a newer compiler decided that it was fine to vectorise that code (since it clearly aligned) the CPU was no longer happy with it.

I think parent commenter made a joke. UB can be seen as "implementation defines this to reformat your hard drive. No we don't document it".

That is, the compiler de facto defines what happens when you compile UB code.

So you're not wrong, but I think you missed the sarcastic spin of parent.

>That is, the compiler de facto defines what happens when you compile UB code.

That is not what undefined behavior is though, that is unspecified behavior.

The entire point of undefined behavior is to cover the cases where the compiler can't define the semantics of your program either because doing so is genuinely not possible, or is incredibly onerous to deduce, or would require introducing runtime checks whose performance cost is at odds with C and C++'s predominant use cases.

Sorry, by "de facto defines" I meant that it factually does something, even if that "something" is "segfault the compiler at build time".

That "de facto" did some heavy lifting.

Except that UB doesn't mean that. UB means "the developer must never write this".
Both are wrong. It means "this standard does not constrain the behaviour of code that does this".

It's entirely legal for implementations to have predictable behaviour, documented or not, for code that is undefined by the standard. In their quest for maxxing benchmark performance they generally choose not to, but there's really nothing in any standard that stops you from making an implementation that prioritises safety.

Every implementation so far has predictable behavior in all cases. Sometimes the rules for predicting it are very obscure. But it's all fully defined within the compiler's binary code. And none of them link to nasal portals.
How do you propose to predict the behavior of a true race condition with only the binary, faithfully translated by the compiler?

Moreover, this is at best an incredibly pedantic point, not something that changes how programmers need to approach UB. You can't review the source code of a compiler that hasn't been written yet.

Print x twice. Not all “side effects” care about order.

Better yet, define an order for parameter evaluation.

There is an easy way to take control: read the volatile variable only once.

  volatile int x = 5;
  ...
  int y=x;
  printf("%d in hex is 0x%x.\n", y, y);
You're missing the point. Volatile forces two loads of a value that may have changed in the middle. So the value of "x" may depend on the time/order of load.
Which is, if I understand correctly, the entire point of volatile. Don't use it if you don't want that behavior.

And in fact, in the example given, if there is something (another thread or whatever) that can change the value of x, then you don't know what either number will be. Well, in that circumstance, without volatile, it may print the same number both times, but you still don't know what the number will be (unless the read gets optimized away entirely).

If that behavior is the entire point, then I think the bigger point is that the spec should reflect that and not call it undefined.
I suspect that many undefined behaviors reflect the inability of the standard committee to come to a consensus on the nuances involved. “Punt to the implementers” is a way to allow every tool vendor to select their own expected behavior in those cases.
You seem to be operating under the assumption "undefined behavior" means "the compiler authors can decide what to do." That's not what it means. It means "any program that causes this behavior to be triggered is not a valid C program, the programmer knows this and did not submit an invalid program, and the programmer explicitly prevented this from happening elsewhere in ways automated analysis cannot detect. Proceed with compilation knowing this branch is impossible."

The spelling for compiler authors getting to choose a behavior is "implementation defined", as the other comment mentions.

Then it should be "implementation defined" rather than "undefined".
Why is that missing the point? Loading it twice, possibly with different values, is the intended behavior. It's only undefined because the C spec doesn't specify the order of the loads (unlike most other languages which have a perfectly well-defined order for side effects in a single expression).
What you are describing is implementation defined behavior. Using that is perfectly safe and reasonable. Undefined means this programs is malformed.
No I'm just repeating what the original comment said, which is that it's explicitly UB:

"5.1.2.4.1 says any volatile access - including just reading it - is a side effect. 6.5.1.2 says that unsequenced side effects on the same scalar object (in this case, x) are UB. 6.5.3.3.8 tells us that the evaluations of function arguments are indeterminately sequenced w.r.t. each other."

If function arguments were sequenced with respect to each other, it wouldn't be a problem.

But actually, maybe the original comment is wrong. Presumably "indeterminately sequenced" and "unsequenced" mean different things, although I don't have a copy of the standard at hand to check.

Couldn’t you just define that function arguments are evaluated left to right?

Or just throw an error.

Why? Just for this edge case? It could be faster and/or allow smaller code size to allow this to be undefined.

Undefined is also different from "depends on the compiler", because which behavior is chosen can even depend on the circumstances, whatever code appears before and/or after it.

That said, UB in code, such as this example of ordering of reads of volatile parameters being undefined, does not automatically mean that code that uses it is bad. It may very well be that the function being called doesn't misbehave either way.

That’s the point of the whole article. It’s not worth the speed gain to have a language that nobody can safely use because you can’t really prevent UB when you write it.

> It may very well be that the function being called doesn't misbehave either way.

The function being good or bad has nothing to do with the UB. The UB occurs before the function is called.

I meant reading the uninitialized variable
There is no uninitialized variable, I explicitly initialized it to 5.

And yes indeed, C could do what Rust does and define the order of evaluation for function arguments.

If the argument expressions are indeed side-effect-less, the compiler can always make use of the "as-if" rule and legally reorder the computation anyway, for example to alleviate register pressure.

HCF
I have good news about what UB allows
What is that?
A fictitious assembly instruction (and pretty good TV series).

https://en.wikipedia.org/wiki/Halt_and_Catch_Fire_(computing...

Halt and Catch Fire
Compilation error
It’s hard to detect all UB at compile time
It’s harder depending on the language, which is clearly the point.