Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

In your example, why should it optimise out the second case? Maybe foo() changed p so it's no longer null.

Compiler writers do not format disks just to punish your UBs.

IMHO if the compiler exploiting UB is leading to counterintuitive behaviour that's making it harder to use the language, the compiler is the one that needs fixing, regardless of whether the standard allows it. "But we wrote the compiler so it can't be fixed" just feels like a "but the AI did it, not me" excuse.



You would need to pass *p or declare it as volatile I assume, otherwise by what means would foo change p?


The address of p could have been taken somewhere earlier and stored in a global that foo accesses, or a similar path to that; and of course, p could itself be a global. Indeed, if the purpose of foo is to make p non-null and point to valid memory, then by optimising away that code you have broken a valid program.

If the compiler doesn't know if foo may modify p, then it can't remove the call. Even if it can prove that foo does not modify p, it still can't remove the call: foo may still have some other side-effects that matter (like not returning --- either longjmp()'ing elsewhere or perhaps printing an error message about p being null and exiting?), so it won't even get to the null dereference.

As a programmer, if I write code like that, I either intend for foo to be doing something to p to make it non-null, or if it doesn't for whatever reason, then it will actually dereference the null and whatever happens when that's attempted on the particular platform, happens. One of the fundamental principles of C is "trust the programmer". In other words, by trying to be "helpful" and second-guessing the intent of the code while making assumptions about UB, the compiler has completely broken the expectations of the programmer. This is why assumptions based on UB are stupid.

The standard allows this, but the whole intent of UB is not so compiler-writers can play language-lawyer and abuse programmers; things it leaves undefined are usually because existing and possible future implementations vary so widely that they didn't even try to consider or enumerate the possibilities (unlike with "implementation-defined").


But in fact compilers do regularly prove such things as, "this function call did not touch that local variable". Escape analysis is a term related to this.

I'm more of two minds about that other step, where the compiler goes like, "here in the printf call the p will be dereferenced, so it surely is non-null, so we silently optimize that other thing out where we consider the possibility of it being null".

Also @joshuamorton, couldn't the compiler at least print a warning that it removed code based on an assumption that was inferred by the compiler? I really don't know a lot about those abstract logic solver approaches, but it feels like it should be easy to do.


You don't need to worry about null check removal optimizations unless you do this:

    int main() {
      char *p;
      p = mmap(0, 65536, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
      // ...
      return __builtin_popcountl((uintptr_t)p);
    }
Or you do this:

    void ContinueOnError(int sig, siginfo_t *si, ucontext_t *ctx) {
      xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_LONG_64);
      xed_instruction_length_decode(&xedd, (void *)ctx->uc_mcontext.rip, 15);
      ctx->uc_mcontext.rip += xedd.length;
    }

    int main() {
      signal(SIGSEGV, ContinueOnError);
      volatile long *x = NULL;
      printf("*NULL = %ld\n", *x);
    }


warning that it removed code based on an assumption that was inferred by the compiler

That would dump a ton of warnings from various macro/meta routines, which real-world C is usually peppered with. Not that it’s particularly hard to do (at the very least compilers know which lines are missing from debug info alone).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: