The problem is that C developers want their code to work as they expect, which is reasonable, and at the same time they want their code to be as fast as possible, which is also reasonable. But in practice these two desires are often at odds. So when C compilers start exploiting more UB in order to make code faster programmers turn around and complain, and then when C turns out to be slower than Fortran due to a lack of UB-exploiting optimizations a different segment of programmers will complain. It's a difficult balancing act for compiler and language developers.
I'm starting to think we need a way to allow UB-based optimisations on a per-function or per-block basis, with the rest of the code being compiled with the simplest mental model. It's getting a bit hard to reason about whether your code is actually safe or not, it would be better to compile most code as if all pointers can alias, integers wrap around, accessing nullptr is allowed, etc. (subject to per-platform quirks) Then we can mark the hot functions as candidates for better optimisation. Maybe other than an attribute we could add assertions to the code, e.g. assert_no_overlap(ptr1, size1, ptr2, size2) -- assert that the range [ptr1, ptr1+size1) does not overlap [ptr2, ptr2+size2). That could then optionally be compiled into an actual runtime assertion to help catch UB at runtime.
Ultimately the C (and C++) memory model is meant to help us write fast, safe software. Developers must be able to reason about the semantics of their code. Optimising short parts of the program that can be reasonably checked by a person for not invoking UB is probably the right way and will result in fast software with fewer bugs due to programmer error.
Edit: Not sure why people are downvoting this, it's like there's some UB religion out there. Do you people want your programs to crash due to the compiler doing surprising things?
> Do you people want your programs to crash due to the compiler doing surprising things?
I obviously can't speak for everyone who defends this sort of compiler behavior, but my preferred solution is for the compiler to continue performing these kinds of optimizations while also providing better static analysis and sanitizer tools to catch problems earlier, even if that involves extending or replacing the language to make that possible.
Expecting everyone who writes C or C++ to do this kind of reasoning in their head and perform these optimizations manually (or drop their performance back toward -O0) just sounds like it would make things worse.
This already exists. gcc has optimization pragmas and function attributes that allow optimization to be set on a per-function level. However, they don't really work as expected, mainly because inlining exists. Example:
int foo(void) { return 42; }
int bar(void) { return foo() + foo(); }
If the whole file is compiled as -O0, foo and bar must be compiled as-is. If the whole file is compiled as -O2, bar will be optimized to "return 84;". But what if foo is compiled as -O0 and bar is compiled as -O2? Can bar still be optimized to "return 84;"? What about "return 42+42;"? Can it do "int x = foo(); return x+x;"? What about "int x = foo(); if (x == 42) return 84; else return x+x;"? All of these are permitted in -O2 if gcc thinks it's a good idea, but in our "mixed mode" the semantics are unclear. It might be theoretically possible to come up with a consistent definition; it might start with "functions specified as -O0 cannot be inlined or have other functions inlined into them", but there are still more things to consider, like the behavior of global memory, thread-local storage, floating-point environment, and so on (what if one or more functions is static inline or __attribute__((always_inline))?).
The real solution with current gcc is to simply compile different files ("translation units") with different optimization flags if you want. This has always been supported. Unfortunately, it comes with a potentially significant performance penalty; the whole point of LTO is to avoid this performance penalty, but this once again returns the issue of the semantics of mixing functions with different optimization levels.
I think "restrict" is a really nice compromise in C: Your code declares that you're willing to follow additional rules, and the compiler can make additional optimizations.
The article shows a piece of code that makes that promise and then doesn't hold up its part of the agreement. I can't even follow the rest of the argument from there because it's all based on a faulty foundation.
> The article shows a piece of code that makes that promise and then doesn't hold up its part of the agreement.
Edit: disregard; I now see your other comments.
Incorrect. The article shows a piece of code that makes and fulfills that promise, and then the optimization passes break the code. If you disagree, I would be interested in hearing how/where you think the code itself is faulty.
No, I was sloppy when I read it the first time. The second snippet of code is broken, and I (incorrectly) assumed it was a valid translation from the first snippet. My bad.
The first snippet is subtle, and I'm not a language lawyer, but I can't see anything that screams "contract violation".