4 ms·
Reading uninitialized memory is not UB... but anyway. Here's an example of how compilers treat null dereferencing as UB: void func(struct x *aptr, struct x
by klodolph 5y ago
Reading uninitialized memory is not UB... but anyway. Here's an example of how compilers treat null dereferencing as UB:
void func(struct x *aptr, struct x *bptr) {
x->a = 5;
y->a = 10;
x->a = 10;
}
This is a contrived example but you can imagine writing code that makes a sequence of assignments like this. An optimizing compiler can remove the "dead store" which assigns 5 to x->a. However, this optimization is valid because writing to a null pointer is undefined... if writing to a null pointer trapped (SIGFAULT), which is what it actually does at runtime, then the optimization would be incorrect... because you could observe the intermediate value of x->a = 5.
The compiler is not really "detecting that your program has undefined behavior". Instead, it is assuming that undefined behavior doesn't happen, and optimizes your program using that assumption. It's unclear what "detecting undefined behavior" would mean here... what kind of diagnostic the compiler should emit, or how to suppress the diagnostic. It's clear that this simple code is pervasive, and the preferred approach for dealing with it is somewhat outside the scope of a normal C compiler... something like static analysis, runtime instrumentation, or some kind of formal methods.
- foxfluff 5y ago> However, this optimization is valid because writing to a null pointer is undefined... if writing to a null pointer trapped (SIGFAULT), which is what it actually does at runtime, then the optimization would be incorrect... because you could observe the intermediate value of x->a = 5. I'm not sure this is a good example, because there's no way for you to read them from the same thread (and reading from another thread would be a race). Reading them from a signal handler would yield unspecified values (unless you're using lock-free atomic types, in which case we're probably not worried about optimizing these assignments). > When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects that are neither lock-free atomic objects nor of type volatile sig_atomic_t are unspecified, as is the state of the floating-point environment.
- klodolph 5y agoThat's a good point, however I think it's not hard to come up with a different example that shows how compilers assume that pointers aren't NULL. Like, void function(struct x *ptr) { ptr->x = 1; if (ptr == NULL) { some_big_chunk_of_code(); } } The idea is that this could be the result of inlining, where the inlined code does a null check, but in the context it's being inlined into, we already know ptr is not null.
- mjevans 5y agoThe compiler should help the programmer optimize instead. "Warning: dead store detected (file):line clobbered by (file):line. Use keyword volatile to always store or remove (file):line." This way the optimization is promoted from the resulting binary to the source code, and bugs / typos / etc can be corrected.
- foxfluff 5y agoOptimizations often deal with inlined code. The code in the inlined function might be fine in the general sense, but once taken in a particular context, there's dead or redundant code because the compiler can deduce values from context. It might even all fold into a constant. Telling the developers to optimize these would be equivalent to telling them to start inlining their code (or creating variants of every function that might be inlined, to optimize it for the specific context where it will be inlined).
- gpderetta 5y agoYou would get literally millions of such warnings on your average C codebase.
- zaphar 5y agoSo basically you are saying that in the average C codebase there are millions of landmines waiting to silently fail on you. This is not a ringing endorsement of the language it's more like a ringing indictment instead.