For example, please explain how to prevent this without also (inadvertently) preventing the removal of unnecessary null checks when functions are inlined. What about an unnecessary null check that's hidden inside a macro? What about whole program LTO?
A macro could be used in a variety of situations. In some of them, you need the null check for safety. In others, the null check in entirely redundant. Short of AGI, the compiler can't actually comprehend the code it operates on. Yet surely you expect it to eliminate "obviously" redundant work? It accomplishes this by applying a large set of fairly simple rules.
(There are probably much better examples but I can't think of them off the top of my head.)
In practice, when I play with Godbolt I find that the "obvious" cases tend to result in helpful diagnostic messages.
If a null check is unnecessary, that's because it is reachable only from the not-null side of some previous null check. The compiler can track that information just fine, using the same tools it uses to track false information derived pointer dereferences.
If:
if(!p) abort();
// we know p is non-null here, regardless of the dereference
use(*p);
MACRO(p);
expands to: if(!p) abort(); // first null check (this is relevant to optimizing out unnecessary null checks)
use(*p); // pointer dereference (this isn't)
if(!p) abort(); // second null check (unnecessary *because of the if*)
utilize(*p); // more pointer dereference
the compiler can optimize out the second if (the "unnecessary null check" you refer to) based on the first if, regardless of whether the pointer dereference is even there. void foo(T *p) {
if (!p) abort();
bar(p);
}
void bar(T *p) {
use(*p);
MACRO(p);
}
In other words, the first null-check and the latter check are in different functions that may not even be in the same compilation unit, or where the call sequence is hard to reason about due to function pointers etc.This is one of several situations where it might be useful for the compiler to excercise it's perogative to implement operations without regard for undefined behaviour to rewrite `use(*p);` as `if(!p) abort(); use(*p);`, which would make the null check in MACRO unnecessary, but unless it does so, the check is not unnecessary, just insufficient.
No, it is not. For example, the transformation:
is not reasonable, since p is not, in fact, always nonnull.