Hacker News new | ask | show | jobs
by jonathrg 917 days ago
This is because of the calling convention / ABI

If you write

    void free_with_extra_args(int *a, int *b) {
        free(a);
    }
then *a is in already in the correct slot (the RDI register) for the first argument when free_with_extra_args is being called. Whatever is put into *b is never touched. If you compile this with gcc -O2 you get

    free_with_extra_args:
        jmp     free
If you make the function call free(b) instead, you'll have to move b into the right place before calling free:

    free_with_extra_args:
            mov     rdi, rsi
            jmp     free
This is on x86-64 as summarized here https://en.wikipedia.org/wiki/X86_calling_conventions#System...

Wikipedia also has a nice summary of calling conventions on other platforms like ARM. All modern calling conventions are similar: pass the first args in registers and then use the stack as needed https://en.wikipedia.org/wiki/Calling_convention

1 comments

That's only one half of it though, the other interesting part is the jmp (instead of call) to hand over control to a subroutine without pushing a new return address to the stack (since there's no code after the function call, and the calling function doesn't require its own stack frame).
Indeed. Typically you have pairs of call and ret, where call creates a stack frame and ret tears it down

     caller                           caller
       |                                ^
     (call) . . . . . . . . . . . . . (ret)
       |                                |
       V                                |
       outer_function      outer_function
              |            ^
           (call) . . . .(ret)
              |            |
              V            |
              inner_function
A jmp does not modify the stack, so when the inner function calls ret it jumps right back to caller

     caller              caller
        |                  ^
      (call) . . . . . . (ret)
        |                  |
        v                  |
        outer_function     |
              |            |
            (jmp)          |
              |            |
              V            |
              inner_function
This trick stops working as soon as outer_function needs local variables or does anything other than returning the exact return value of inner_function. In that case you need a stack frame