6 ms·
//int dead = *P; // deleted by the optimizer. If a compiler does that, it is broken. Dereferencing P clearly has side effects (i.e. segfault if P does not
by Kenji 10y ago
//int dead = *P; // deleted by the optimizer.
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.
- bodyfour 10y agoWell, by your standard most compilers used today are "broken". Possibly causing a segfault is not a side effect the compiler is required to respect. If you want to force the compiler to not remove a memory operation in C, you need to define the pointer as "volatile"
- Unklejoe 10y agoDoes this actually count as a side effect? 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.
- jschwartzi 10y agoIf your dereference compiles into a read from a register, then in the case that the register is clear-on-read like some interrupt status registers I've seen, the dereference will have unintended side effects.
- Unklejoe 10y agoAgreed. However, I believe that in C, standard variable types are assumed to not have such side effects on a read, which is why such variables must be explicitly declared as "volatile".
- Thiez 10y agoSegfaults are an implementation detail of your hardware architecture. They are not a "thing" in the C standard. In the good old days before protected mode on x86 was a thing (if I recall correctly windows 95 was from this era) most of the time you would not segfault (unless you addressed RAM that wasn't there? I'm not sure anymore). 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.
- fstd 10y agoif P does not point to valid memory, then dereferencing it is UB. Therefore, the compiler is at liberty to do whatever it wants (whether or not that is a good idea is irrelevant for the purposes of this consideration). 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.
- 0xcde4c3db 10y ago> if P does not point to valid memory, then dereferencing it is UB 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.
- lmm 10y ago> IIRC, simply making P point to anything other than an object or 1 past the end of an array is already UB. 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.
- 0xcde4c3db 10y agoYes, NULL is another exception that I forgot. I'm not sure this is ever explicitly stated as a distinct rule in the standard, but it seems to be implied by various arithmetic and conversion operations on pointers being UB if the result doesn't point to a valid object.
- lmm 10y agoThe standard imposes no requirements on how an implementation behaves with a program that does that. While I would agree that that makes C a not-very-useful language for most modern applications programming, what requirements would you have the standard impose? On some architectures reading * P might trap/segfault, so you cannot require that this line be harmless. Other architectures might read a value into dead (e.g. embedded systems with no MMU/segmentation) and continue harmlessly, so you cannot require that the line have some effect. If you make a requirement that if the line segfaults that counts as an externally visible effect then you basically disallow the compiler from reordering memory accesses at all, which could cost a lot of performance. Java shows it's more-or-less possible to achieve close-to-C performance while imposing consistent requirements on null dereferences, but only with the help of a highly-tuned JIT that consumes quite a bit of memory and startup time, which could be prohibitive for many environments.