For most C software on x86_64, UB is "fine" with very strong bunny ears. But it is preferable for one to, shall we say, write UB intentionally rather than accidentally and unknowingly. Having an awareness of all the minefields lends for more respect for the dangers of C code, it makes one question literally everything, and that would hopefully result in more correct code, more often.
On that note, on some RISC-V cores unaligned access can turn a single load into hundreds of instructions.
I think the problem is just that C is under specified for what we expect a language to provide in the modern age. It is still a great language, but the edges are sharp.
However I do agree that just saying "the behaviour is undefined" is an unhelpful cop-out. They could easily say something like "non-atomic misaligned accesses either succeed or trap" or something like that.
> In the end it's the CPU and not the compiler which decides whether an unaligned access is a problem or not.
Not just the CPU - memory decides as well. MMIO devices often don't support misaligned accesses.
(Note the normal way to handle that if the hardware doesn't actually support it is for the access to trap and then the OS or firmware emulates it.)
That means that the compiler must emit the read, even if the value is already known or never used, as it might trap. There is a reason for the UB!
What people want is for a compiler that assumes that all pointers are aligned to use an aligned store or load instruction whenever the compiler wants to issue such an instruction. There is no need for UB here.
In other words, they want the compiler to stick with the decision it made and not randomly say "I can't do the thing I've been doing correctly for decades, because that's UB, my hands are tied, I must ruin the code, there's no other way."
> What people want is for a compiler that assumes that all pointers are aligned to use an aligned store or load instruction whenever the compiler wants to issue such an instruction.
That requires a mental model of a compiler, that runs through the code linearly and emits instructions in the order defined by the code. That's not what is happening. Current compilers model the value flow through the code, and then emit a program that happen to output the same values for the valid input.
> In other words, they want the compiler to stick with the decision it made
What instead often happens is, that the compiler doesn't even emit a decision at all, because that is completely irrelevant.
The suggestion is that each individual access must either succeed or trap. Those are the only possible outcomes, but different accesses can result in different outcomes.
If your proposal includes, that a trapping access gets treated by the compiler as if it didn't and the compiler emits code that performs all kind of side-effects, that are logically independent if it would not trap, before the access, then you are back to undefined behaviour under a different name.
This is in contrast to the other category that exists, which is "implementation-defined".
Just don't do unaligned access, it's a dialect that doesn't exist currently, and should never exist.
That doesn't mean unaligned access would need to be UB. It could be implementation defined. Or could just be defined to result in an error on all machines.
Defining it to always error would add overhead even for proper aligned access on x86, as the generated code would need to explicitly check in many cases.
No, it's a lot better. Because implementations have to define it.
This is plain wrong. Undefined behaviour, means the C standard specifies no restriction on the behaviour of the program, which is what the implementation chooses to emit. An implementation can very well choose to emit any program it pleases, including programs that encrypt your harddisk, but also programs that stick to well defined rules.
Writing C (or any language) means adhering to the standard, because that's the definition of the language.
Side effects matter, and they are always non-portable/implementation defined/dependent on the hardware.
What printf() actaully does is implementation defined - what does "printing mean", does a console even exist? Maybe a user expects it to show graphical ascii/utf8 glyphs on a LCD display? Well, not every computer has that, so now what?
> What printf() actaully does is implementation defined - what does "printing mean", does a console even exist? Maybe a user expects it to show graphical ascii/utf8 glyphs on a LCD display? Well, not every computer has that, so now what?
You can very well write a program, that doesn't make an assumption about any of those things. In fact you should, because the user is to be the arbiter of in what environment your program gets invoked and what it gets connected to. Writing a program that makes assumptions about the specific behaviour of stdout is going to be highly impractical and annoying and also violates the abstraction and interface that stdout is. This consideration isn't just valid for stdout, but also for any other interface your programs naturally interfaces with.
> Well, not every computer has that, so now what?
In the case stdout is not available or can't process your data it is going to return -1 and set errno and then you can deal with that.
Have you heard of Java or even Python or JavaScript?
> Side effects matter, and they are always non-portable/implementation defined/dependent on the hardware.
Granted. But how does the need for implementation defined excuse undefined behaviour?
> What printf() actaully does is implementation defined [...]
> Well, not every computer has that, so now what?
The standard can be written conditionally: 'if the computer has display, printf shall show something.'
The C standard reminds me of Posix. You have a rough guideline if you ever wanted to port a program, but you actually have to learn the new compiler and its actual behavior before doing so.
There's multiple implementations of Python, but you are right that CPython is the big one. Part of that dynamic is that CPython runs on nearly everything well enough (well, as good as Python runs), so that we don't really need multiple implementations. Unlike the first C Compilers.
Of course, Lisp is a family of languages these days. Scheme and Common Lisp have many implementations, but Racket or Arc only have one.
Eh, if the compiler knows that two addresses are different at compile time, it also knows how big the difference is.
† Although on a real modern CPU the pointer "is" just an address, notionally it has three components, the address, an address space (modern machines typically only have one) and a "provenance".
Tbh, already the first example (unaligned pointer access) is bogus and the C standard should be fixed (in the end the list of UB in the C standard is entirely "made up" and should be adapted to modern hardware, a lot of UB was important 30 years ago to allow optimizations on ancient CPUs, but a lot of those hardware restrictions are long gone).
In the end it's the CPU and not the compiler which decides whether an unaligned access is a problem or not. On most modern CPUs unaligned load/stores are no problem at all (not even a performance penalty unless you straddle a cache line). There's no point in restricting the entire C standard because of the behaviour of a few esoteric CPUs that are stuck in the past.
PS: we also need to stop with the "what if there is a CPU that..." discussions. The C standard should follow the current hardware, and not care about 40 year old CPUs or theoretical future CPU architectures. If esoteric CPUs need to be supported, compilers can do that with non-standard extensions.