4 ms·
Your otherComputation() example doesn't strike me as a clear cut case. My preference (and I agree that this is not required by spec) would be that the checks
by nkurz 11y ago
Your otherComputation() example doesn't strike me as a clear cut case. My preference (and I agree that this is not required by spec) would be that the checks would be omitted only if the dereference has actually been executed, and not if the initialization has just been declared. Essentially, I think I'd like dereference of a NULL pointer to be defined behavior as far as generated code (the code must behave identically to how it would behave if the load was issued) while possibly undefined for the processor (crashing is valid, but returning a constant would be valid too). The compiler is allowed to reduce many checks to one by assuming consistency, but not allowed to ignore the checks altogether.
I'm pretty sure your snippet invokes nasal demons in all dialects. Dereferencing NULL is a pretty classic case where anything can happen once you do it.
Is the dereferencing itself undefined in all dialects, or is it only undefined if one later "evaluates" the dereferenced value? Or do some define evaluation differently? For example (to my surprise) a comment by 'lkasndasd' on the corresponding Reddit thread points out that &* ptr is never undefined behavior in C, since the address operator and the dereference operator cancel out even (explicitly) if ptr is NULL:
6.5.3.2 Unary arithmetic operators, Semantics, paragraph 3:
The unary & operator yields the address of its operand.
[...] If the operand is the result of a unary * operator,
neither that operator nor the & operator is evaluated and
the result is as if both were omitted, except that the
constraints on the operators still apply and the result is
not an lvalue.
and footnote 102:
Thus, &*E is equivalent to E (even if E is a null pointer) [...]
https://www.reddit.com/r/programming/comments/3xk6zj/the_dangers_of_super_smart_compilers/cy5gi1t https://www.reddit.com/r/programming/comments/3xk6zj/the_dan...
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
- mikeash 11y agoThat quote is interesting, but the fact that they have to explicitly call it out implies to me that the mere act of writing the dereference is undefined behavior for NULL in all other cases. I'm pretty sure the dereference is immediately UB (other than the case you quote here). Consider that when optimizations are off, that line will probably immediately execute a load instruction, generating a segfault.
- nkurz 11y agoI thought it would be interesting to see how some different compilers actually behave here. This was my test program: nate@haswell:~/tmp$ cat deref.c /* gcc -fno-inline -O3 -g -Wall deref.c -o deref */ /* (or as desired to show particular outcome) */ #include <stdio.h> #include <stdlib.h> void deref_and_print(int *ptr) { int i = *ptr; /* undefined behavior if ptr is NULL */ if (ptr == NULL) printf("NULL\n"); else printf("%d\n", i); } int main(int argc, char **argv) { int *ptr = NULL; if (argc <= 1) { printf("Usage: %s [arg]\n", argv[0]); printf(" Demonstrates undefined behavior if 'arg'"); printf(" is not a positive integer\n"); exit(1); } int i = atoi(argv[1]); if (i > 0) ptr = &i; deref_and_print(ptr); exit(0); } As you guessed, icc, gcc, and clang all crash with a segfault for 'deref 0' when compiled with -O0. But more interestingly, when compiled with -O1, -O2, or -O3, clang prints "NULL", while icc and gcc continue to crash.
- mikeash 11y agoNice test. Surprising results for clang. I take it that it doesn't crash for you when it prints NULL? That's the case for me. I'd have thought it would either remove the NULL check and crash, or leave it in but continue to dereference NULL, and thus crash.
- saurik 11y agoThe code when it doesn't crash has been reorganized by the compiler so the indirection doesn't even happen until it is preparing the arguments for the printf, which is in an else after the NULL check. If you don't need the value of a computation in one of two sides of an if statement and you know there are no legitimate side effects (I say "legitimate", as crashing does not count) to the calculation, it makes sense to delay the execution of that logic until and unless you actually need it done. I thereby don't find either result surprising here: it is just a demonstration of different optimization decisions and abilities of the two compilers.