1)How C structures get passed to function in assembly. I mean pass by value, not pass by reference. 2)By the way, how callees return structure to its callers? I\'m so sorry for
As has been pointed out by others - passing structures by value is generally frowned upon in most cases, but it is allowable by the C language nonetheless. I'll discuss the code you did use even though it isn't how I would have done it.
How structures are passed is dependent on the ABI / Calling convention. There are two primary 64-bit ABIs in use today (there may be others). The 64-bit Microsoft ABI and the x86-64 System V ABI. The 64-bit Microsoft ABI is simple as all structures passed by value are on the stack. In The x86-64 System V ABI (used by Linux/MacOS/BSD) is more complex as there is a recursive algorithm that is used to determine if a structure can be passed in a combination of general purpose registers / vector registers / X87 FPU stack registers. If it determines the structure can be passed in registers then the object isn't placed on the stack for the purpose of calling a function. If it doesn't fit in registers per the rules then it is passed in memory on the stack.
There is a telltale sign that your code isn't using the 64-bit Microsoft ABI as 32 bytes of shadow space weren't reserved by the compiler before making the function call so this is almost certainly a compiler targeting the x86-64 System V ABI. I can generate the same assembly code in your question using the online godbolt compiler with the GCC compiler with optimizations disabled.
Going through the algorithm for passing aggregate types (like structures and unions) is beyond the scope of this answer but you can refer to section 3.2.3 Parameter Passing, but I can say that this structure is passed on the stack because of a post cleanup rule that says:
If the size of the aggregate exceeds two eightbytes and the first eightbyte isn’t SSE or any other eightbyte isn’t SSEUP, the whole argument is passed in memory.
It happens to be that your structure would have attempted to have the first two 32-bit int
values packed in a 64-bit register and the double
placed in a vector register followed by the int
being placed in a 64-bit register (because of alignment rules) and the pointer passed in another 64-bit register. Your structure would have exceeded two eightbyte (64-bit) registers and the first eightbyte (64-bit) register isn't an SSE register so the structure is passed on the stack by the compiler.
You have unoptimized code but we can break down the code into chunks. First is building the stack frame and allocating room for the local variable(s). Without optimizations enabled (which is the case here), the structure variable s
will be built on the stack and then a copy of that structure will be pushed onto the stack to make the call to print_student_info
.
This builds the stackframe and allocates 32 bytes (0x20) for local variables (and maintains 16-byte alignment). Your structure happens to be exactly 32 bytes in size in this case following natural alignment rules:
6fa: 55 push %rbp
6fb: 48 89 e5 mov %rsp,%rbp
6fe: 48 83 ec 20 sub $0x20,%rsp
Your variable s
will start at RBP-0x20 and ends at RBP-0x01 (inclusive). The code builds and initializes the s
variable (student
struct) on the stack. A 32-bit int 0xa (10) for the age
field is placed at the beginning of the structure at RBP-0x20. The 32-bit enum for Man
is placed in field gen
at RBP-0x1c:
702: c7 45 e0 0a 00 00 00 movl $0xa,-0x20(%rbp)
709: c7 45 e4 00 00 00 00 movl $0x0,-0x1c(%rbp)
The constant value 1.30 (type double
) is stored in memory by the compiler. You can't move from memory to memory with one instruction on Intel x86 processors so the compiler moved the double value 1.30 from memory location RIP+0x100 to vector register XMM0 then moved the lower 64-bits of XMM0 to the height
field on the stack at RBP-0x18:
710: f2 0f 10 05 00 01 00 movsd 0x100(%rip),%xmm0 # 818 <_IO_stdin_used+0x48>
717: 00
718: f2 0f 11 45 e8 movsd %xmm0,-0x18(%rbp)
The value 3 is placed on the stack for the class
field at RBP-0x10:
71d: c7 45 f0 03 00 00 00 movl $0x3,-0x10(%rbp)
Lastly the 64-bit address of the string Tom
(in the read only data section of the program) is loaded into RAX and then finally moved into the name
field on the stack at RBP-0x08. Although the type for class
was only 32-bits (an int
type) it was padded to 8 bytes because the following field name
has to be naturally aligned on an 8 byte boundary since a pointer is 8 bytes in size.
724: 48 8d 05 e5 00 00 00 lea 0xe5(%rip),%rax # 810 <_IO_stdin_used+0x40>
72b: 48 89 45 f8 mov %rax,-0x8(%rbp)
At this point we have a structure entirely built on the stack. The compiler then copies it by pushing all 32 bytes (using 4 64-bit pushes) of the structure onto the stack to make the function call:
72f: ff 75 f8 pushq -0x8(%rbp)
732: ff 75 f0 pushq -0x10(%rbp)
735: ff 75 e8 pushq -0x18(%rbp)
738: ff 75 e0 pushq -0x20(%rbp)
73b: e8 70 ff ff ff callq 6b0 <print_student_info>
Then typical stack cleanup and function epilogue:
740: 48 83 c4 20 add $0x20,%rsp
744: b8 00 00 00 00 mov $0x0,%eax
749: c9 leaveq
Important Note: The registers used were not for the purpose of passing parameters in this case, but were part of the code that initialized the s
variable (struct) on the stack.
This is dependent on the ABI as well, but I'll focus on the x86-64 System V ABI in this case since that is what your code is using.
By Reference: A pointer to a structure is returned in RAX. Returning pointers to structures is preferred.
By value: A structure in C that is returned by value forces the compiler to allocate additional space for the return structure in the caller and then the address of that structure is passed as a hidden first parameter in RDI to the function. The called function will place the address that was passed in RDI as a parameter into RAX as the return value when it is finished. Upon return from the function the value in RAX is a pointer to the address where the return structure is stored which is always the same address passed in the hidden first parameter RDI. The ABI discusses this in section 3.2.3 Parameter Passing under the subheading Returning of Values which says:
- If the type has class MEMORY, then the caller provides space for the return value and passes the address of this storage in %rdi as if it were the first argument to the function. In effect, this address becomes a “hidden” first argument. This storage must not overlap any data visible to the callee through other names than this argument. On return %rax will contain the address that has been passed in by the caller in %rdi.
There's no general answer to your question - every compiler works differently and can do things differently according to what optimisations you select. What you've observed is a common optimisation - the first few parameters of suitable types are passed in registers, with extra and/or complex ones passed on the stack.
It depends on the ABI for your system. On x86_64, most systems use SYSV ABI fo AMD64 -- the exception being Microsoft, who use their own non-standard ABI.
On either of those ABIs, this structure will be passed on the stack, which is what is happening in the code -- first s
is constructed in main
's stack frame, then a copy of it is pushed on the stack (the 4 pushq instructions).