How do you feel about:
void foo1(int *p) {
*p = 7;
printf("%d\n", *p);
free(p);
}
May the compiler replace that with puts("7") and free()? Recall that free() is a no-op when the pointer is NULL.
Are you arguing that each function may reduce pointer accesses down to one but not down to zero? What about
int foo2() {
int *p = malloc(400);
*p = 0;
/* ... code here was inlined then optimized away ... */
ret = 7;
free(p);
return ret;
}
? May we remove '*p = 0;', whether we remove the malloc+free or not?
Generally speaking the compiler tries not to reason about the whole function but just to look at the smallest possible collection of instructions, like how add(x, x) can be pattern matched to mul(x, 2) and so on. Having to reduce to one but not zero memory accesses per function is not easily compatible with that model. We would have to model branches that make the access conditional, the length of accesses may differ (what if 'p' is cast to char* and loaded), read vs. write, multiple accesses with different alignment, and maybe other things I haven't considered.
Both gcc and clang provide -fsanitize=null which checks all pointer accesses for null-ness before performing them. These can be surprising, your code (libraries you didn't write, headers you included) may be dereferencing NULL and relying on the optimizer to remove the invalid access. IMO there should be a "pointer is null-checked after use", it's a clear example where the programmer wrote something other than what they intended.