Perhaps we need a -Wundefined-behaviour so that compilers print out messages when they use those type of 'tricks'. If you see them you can then choose to adjust your code in a way that it follows defined path(s) of the standard(s) in question.
Perhaps we need a -Wundefined-behaviour so that compilers print out messages when they use those type of 'tricks'. If you see them you can then choose to adjust your code in a way that it follows defined path(s) of the standard(s) in question.
#define FOO 17
void bar(int x, int y) {
if (x + y >= FOO) {
//do stuff
}
}
void baz(int x) {
bar(x, FOO);
}
the compiler can inline the call to bar in baz, and then optimise the condition to (x>=0)… because signed integer overflow is undefined, so can’t happen, so the two conditions are equivalent.The countless messages about optimisations like that would swamp ones about real dangerous optimisations.
My understanding is that undefined behavior is in the spec because the various physical machines that C could compile to would each do something different. So to avoid stepping on toes the spec basically punted and said we are not going to define what happens. Originally this was fine. the C compiler would dutifully generate the machine instructions and the machine would execute them returning the result. The wrench was thrown into the works when we started getting optimizing compilers. I would argue the correct interpretation of "undefined behavior" in the face of a optimizing compiler is "unknown" but then it would sort of behave like the sql NULL(each unknown is different from every other unknown) and act as an optimization fence. When you have to ask the machine what the result of this operation is, it is hard to optimize around it ahead of time.
So my best guess as to why they decided to read "undefined" as "can not happen" is that this is the interpretation they can best optimize for. And nobody really pushed back because what the hell does "undefined" mean anyway? My read is that if the spec wanted to say "can not happen" the spec would have said "can not happen"
> Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to […]
And I think this is where “can’t happen” comes in: in the case of undefined behaviour, the compiler is free to emit whatever it pleases, including pretending it cannot happen!
for (int i=0, j=0; i < N && j < M; i+=2, j++)
i++ looks like i+=1 so fits better in loops iterating over multiple things.Also that int says "these values don't overflow" very concisely and the more modern cleverer C++ using iterator facades probably fails to communicate that idea to anyone.
edit: amused to note on checking for typos that I wrote that with < instead of != which violates your other recommendation, which is one I'm in favour of in theory but apparently don't write out by default.
If n is of type size_t, yes please!
Also every for (size_t i = sizeof(var)-1; i >= 0; --i)
(I've been burned by that one more often than I want to publicly admit).
warn.c:6:21: warning: comparison of integer expressions of different signedness: ‘int’ and ‘size_t’ {aka ‘long unsigned int’} [-Wsign-compare]
6 | for (int i = 0; i < n; i++)
| ^
warn.c:10:36: warning: comparison of unsigned expression >= 0 is always true [-Wtype-limits]
10 | for (size_t i = sizeof(var)-1; i >= 0; --i)
| ^~(even for the relatively high-profile and easy-sounding "detect if the compiler has deleted a line of code in a way which alters observable behavior of the program"!)
Undefined shouldn't mean "whatever", it should be an opportunity to consider what makes the most sense.
Because that's what people actually want. There's no untapped market for a C compiler that doesn't aggressively optimize. The closest you'll get is projects selectively disabling certain optimizations, like how the Linux kernel builds with `-fno-strict-aliasing`.
There probably isn't now, because people who want sanity have moved on from C.I do think that years ago a different path was possible.
> The closest you'll get is projects selectively disabling certain optimizations, like how the Linux kernel builds with `-fno-strict-aliasing`.
Linux also does `-fno-delete-null-pointer-checks` and probably others. I don't think they have a principled way of deciding, rather whenever an aggressive optimization causes a terrible bug they disable that particular one and leave the rest on.
That said, ICC and MSVC can achieve similar or better results without going insane with UB, so it's clearly not as strictly necessary as the lame pedants want you to believe.