I don't think this is true. Look at Zig, they don't seem to have a problem removing a lot of C's undefined behaviour, while still being able to surpass C in speed in many cases.
> Simple example: accessing an array past it's end it's undefined behaviour. If you want to remove this, you need to add bounds checking
Zig does indeed do bounds checking. I think the way it can still compete with C is: - Zig should be better at propagating constants (Better module system, Link-Time Optimization by default, and I think avoiding undefined behaviour helps here too). Arrays/slices do often have constant bounds. - You can choose to build with or without bounds checks (--release-safe, --release-fast). This means you're more likely to discover out of bounds problems during debugging, since you'll always get errors in those cases. But you have the option to release a fast version.
Julia has another interesting solution to bounds checking, where you can mark a piece of code with @inbounds to declare that you assume array access is within bounds.
I think some undefined behaviour can also be detrimental to performance. If you pass two pointers to a function, and it's undefined whether they alias or not, there are optimisations you can't do.
I think this results from a misunderstanding of how undefined behaviour works in C. When a program exhibits undefined behaviour it is not a valid C program. The compiler may just assume (instead of having to prove) that it doesn't happen.
Example: the memcpy(3) standard library function. C says the behaviour is undefined if the given areas overlap. That means the implementation can perform optimizations "knowing" that there is no overlap. A valid C program can't possible invoke memcpy with buffers aliasing each other (because then, the program would be invalid). The compiler is not required to issue a diagnostic about these kinds of incorrect programs and just compiles your code assuming they don't exist.
This is absolutely correct. The specs of C allow so much undefined behavior in order to let the compiler just emit the instructions for the arithmetic operation or memory access or whatever. For edge cases like overflow and bounds, C deliberately says "not my problem" and you just get whatever that hardware architecture happens to do with that instruction. It's a deliberately leaky abstraction.
But, as much as people want to blame optimizing compilers for their behavior around UB, absolutely no one is going to sacrifice C's performance for safety.
> Simple example: accessing an array past it's end it's undefined behaviour. If you want to remove this, you need to add bounds checking (either to raise an error or to just return 0).
What's wrong with saying "it will either return an unspecified value or trap"?
There was a great talk from a C/C++ compiler writer about why you can't remove undefined behaviors while at the same time keeping C like speed.
Simple example: accessing an array past it's end it's undefined behaviour. If you want to remove this, you need to add bounds checking (either to raise an error or to just return 0).