logoalt Hacker News

adzmtoday at 3:15 PM4 repliesview on HN

i have never before thought that a function could 'fall through' to another function. why does this behavior even exist?


Replies

kzrdudetoday at 3:19 PM

Well you leave the C++ realm (execution model), as you should with UB and it depends on implementation. The implementation of the compiler was such that the two functions are placed after each other in the machine code; and if the first function doesn't return, then you continue executing into the code for the next function.

show 2 replies
echoangletoday at 3:40 PM

I'm also confused that an uncalled function is even compiled and linked, wouldn't it make sense to remove it entirely if the compiler can detect that it's never called?

show 1 reply
apple1417today at 3:55 PM

The assembly gives a bit of a hint as to what's happening.

    main:
    
    unreachable():
            push    rbx
            ...
Due to the undefined behavior, it decides calling main must be impossible, so the easiest thing to do is just give up, don't bother defining the rest of it. You can also do the same with std::unreachable(). But the label for the function still sticks around for some reason, so when you jump to it, it falls through. Which leads to the really stupid fact that reordering the functions changes the behavior.

I assume there are good reasons they can't just completely delete the label. Maybe it would screw linking, or with cases where you deliberately have multiple labels for the same function. And if the effect is only visible due to undefined behavior, it's not technically wrong. But I have always thought this is such a stupid case, surely it can't be that complex to add a trap instruction, even in an optimized build you shouldn't really care if it slows down a function that's "never called".

show 1 reply
rcxdudetoday at 3:49 PM

The CPU doesn't really see functions, it just sees instructions. Functions are a convention on top of the machine code. What happens in this case is the compiler emits essentially a malformed function: it ends without performing a return, so execution just continues into the next function in memory. You can get the same behaviour by missing a 'return' statement from a function that needs one (though in that case I've also seen kind of the opposite: the function returns into the function two slots up in the stack, essentially returning from the function that called it! Undefined behaviour can utterly destroy normal control flow).

Probably the process was one optimization pass saw that the function will never return due to an infinite loop, and removed the function return from the IR of the function, then a later pass saw that the infinite loop was a no-op and undefined so removed that as well, leaving a function that basically did nothing, not even return.

show 1 reply