If you want to force the compiler to not remove a memory operation in C, you need to define the pointer as "volatile"
If the C standard did not allow pointer dereferences to be optimized out, many optimizations would become impossible, and people would switch to a language that did allow this.
I didn't think that dereferencing a variable could ever be the sole cause of a side effect. I thought that side effects were only relevant in the context of a store (except for maybe volatile variables in which there are some ordering/barrier constraints applied as well). I realize that in practice, deferencing NULL can definitely cause a crash, but I don't know if it counts as a "side effect" in the typical "C" sense of the phrase.
I know that modifying the value of a variable which is accessed by means of a reference passed as an argument could definitely have side effects (since another unrelated function might read that data) and thus a store can’t be completely optimized away (but still can be reordered), but I don’t see why “dead” can’t be optimized away in this case.
In this case “dead” is a local variable, thus it can never be accessed by anyone outside of the function. Therefore, if there are no reads of the variable after that single store, I don’t see why the store can’t be completely eliminated.
In fact, this is a common issue when dealing with microcontrollers where configuration registers are sometimes written by assigning a value to a dereferenced address constant. That's why they get casted as volatile.
If the compiler knows that this is undefined, then you have already broken your end of the deal and therefore the compiler is free to break their end of the deal.
Therefore, eliminating the line is completely okay and the only think broken there is the code.
IIRC, simply making P point to anything other than an object or 1 past the end of an array is already UB. I suspect that this is because some architectures have dedicated address registers and can take an exception when an invalid address is loaded/calculated instead of doing it when the memory access happens.
I don't think this is true in the strict sense you're describing it. Certainly forming a null pointer isn't UB as I understand it, only dereferencing one.
If a compiler does that, it is broken. Dereferencing P clearly has side effects (i.e. segfault if P does not point into valid memory). A compiler must prove that its optimisation does not affect the program execution. This is clearly a change that does. It cannot optimize away operations with side effects. And if this is a valid optimisation according to the standard, then the standard is broken.
EDIT: If segfaulting is not a part of C, but a part of the hardware architecture, then the compiler shouldn't assume that a dereferenced pointer is not NULL. Either way, the compiler should behave in a way to avoid the least expected outcomes.