Hacker News new | ask | show | jobs
by ericbb 3156 days ago
Thanks for the detailed response!

My point was that the beginning of foo() is compiled to the following (by my compiler):

    push   rbp
    mov    rbp,rsp
    lea    rax,[rbp+0x10]
    mov    QWORD PTR [rbp+0x10],rax
Instead of passing the struct in registers (rdi, rsi, rdx) or passing a pointer to the object (pass by reference), the caller places the object on the stack and the callee knows where to find it just based on the type of the function (it's at [rbp+0x10]).

On the other hand, if you delete the c field from the struct type, then the beginning of foo() looks like this:

    push   rbp
    mov    rbp,rsp
    sub    rsp,0x10
    mov    rax,rdi
    mov    rcx,rsi
    mov    rdx,rcx
    mov    QWORD PTR [rbp-0x10],rax
    mov    QWORD PTR [rbp-0x8],rdx
    lea    rax,[rbp-0x10]
    mov    QWORD PTR [rbp-0x10],rax
In that case, the struct was passed in via registers rdi, rsi (similar to tom_mellior's example) and subsequently copied to the stack (at [rbp-0x10] this time).

So, in neither case would I consider it "pass by reference" because neither leads to a pointer to the x object being passed in as an argument.

It's interesting to learn that Windows does it differently and does in fact use what I'd call "pass by reference". The only ABI I'm (somewhat) familiar with is Linux x86-64.

Your point about the annoying limitations on return values is a good one too. I had the thought at one point that it'd be possible to uniformly use structs with just one field instead of typedefs but the limitations on return value calling conventions mean that the two alternatives do not lead to equivalent code, surprisingly.