|
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. |