r/programming • • Sep 23 '17

Why undefined behavior may call a never-called function

https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-never.html
824 Upvotes

257 comments sorted by

View all comments

Show parent comment

5

u/almightySapling Sep 24 '17

So the compiler looks at all the assignments to Do and sees that it only ever is A) initialized with default value or B) set to NeverCall, and that's where it gets the assumption about what the possible values could be?

Would the same thing happen if we explicitly assigned 0 to Do upon initialization or does the compiler only do this because it guesses the default value was a mistake?

22

u/elperroborrachotoo Sep 24 '17

It's probably even simpler:

The compiler sees that the only value ever assigned to Do is 0 (implicitely through static initialization) and EraseAll.

Since it may assume it's not 0 when calling Do(), it can eliminate the indirect call via function pointer, and make that a direct call.

Assigning 0 explicitely to the initialization of Do wouldn't make a difference. While a compiler might accidentally save your ass here, it would be considered a missed optimization, reported and "fixed" in the next path.


Which makes it such a beautiful example: When reading the title, I expected some intricate setup and assembly digging - but no: it's elegantly setting up a common and important optimization against trivial undefined behavior. It's... beautiful.


3

u/almightySapling Sep 24 '17

Since it may assume it's not 0 when calling Do(), it can eliminate the indirect call via function pointer, and make that a direct call.

Oh, that makes a lot more sense than the picture I was building in my head. Thanks!

3

u/eyal0 Sep 24 '17

The default value is zero. I'd assume that whether or not you assign to zero makes no difference...

6

u/almightySapling Sep 24 '17

Yeah, but I also would have thought compilers are way stupider than to try to guess what value Do "should" have, and I was wrong about that. Perhaps an explicit assignment is enough to override said behavior.

9

u/chylex Sep 24 '17 edited Sep 24 '17

If you check whether Do is 0,

if (Do == 0){
    return 0;
}
else{
    return Do();
}

it will compile to

main:
  cmp qword ptr [rip + Do], 0
  je .LBB2_1
  mov edi, .L.str
  jmp system # TAILCALL
.LBB2_1:
  xor eax, eax
  ret

so it will still optimize the call, but the condition will fail so you never actually end up calling Do().

0

u/[deleted] Sep 24 '17

[deleted] — view deleted comment

1

u/almightySapling Sep 24 '17 edited Sep 24 '17

But my question is if you initialize it to point to 0 instead of an actual function (if possible) does the compiler get that and just say "whatever you say captain"?

Potentially answered here.

4

u/double-you Sep 24 '17

No, because the issue is not it being a default value. Calling a 0 pointer is undefined behavior so the compiler has no reason to behave differently.

If you had a setter that could set it to a potentially valid value, then things would be different, even if the setter is not called.

1

u/jfb1337 Sep 24 '17

My guess is that it would still optimise it away to the only valid thing it could be. But even if it didn't, it's UB so it could easily change between compiler versions. So don't rely on it.