汇编中函数返回结构体的方法
代码生成,函数的返回值是个问题,如果返回值是简单类型,如int, char等,一个字节可以容纳,那编译器的做法是将值直接存在eax寄存器中.
代码为证
c代码:
#include <stdio.h> int add(int a, int b){ return a + b; } int main(){ int a = add(2,3); return 0; }
gcc -S add.c
add.s汇编代码:
.globl add .type add, @function add: pushl %ebp movl %esp, %ebp movl 12(%ebp), %eax movl 8(%ebp), %edx leal (%edx,%eax), %eax popl %ebp ret .size add, .-add .globl main .type main, @function main: pushl %ebp movl %esp, %ebp subl $24, %esp movl $3, 4(%esp) movl $2, (%esp) call add movl %eax, -4(%ebp) movl $0, %eax leave ret
那么如果返回一个结构体,寄存器无法存下怎么办呢?
方法是, 调用者在调用的时候,先把需要的参数反向入栈, 最后再把变量的地址入栈,如 struct A t = get_struct(int a);
则先int a 入栈,然后将t的地址放入eax, pushl %eax.
在被调用的函数中,我们就完成了返回值的赋值工作, 因为有了返回值赋值的变量的地址,所以依次将struct的各成员变量赋值给返回变量的对应内存即可.
好吧,上代码
c代码:
#include <stdio.h> struct A{ int a; char c; }; struct A add(int a, int b){ struct A t; t.a = a*b; return t; } int main(){ struct A t = add(3, 4); printf("%c\n", t.c); return 0; }
gcc -S temp.c
temp.s:
.globl add .type add, @function add: pushl %ebp movl %esp, %ebp subl $16, %esp movl 8(%ebp), %ecx # 8(%ebp)最后一个参数,就是存储了返回变量在调用者中的地址,即调用前main中的%eax的值 movl 12(%ebp), %eax #取出第二个int 参数 imull 16(%ebp), %eax #取出第一个int参数,记住,参数反向入栈,后面的参数在低地址 movl %eax, -8(%ebp) #-8(%ebp) ~ (%ebp)存储的是局部结构体变量t movl -8(%ebp), %eax #依次将局部变量的各个成员变量移动到寄存器 movl -4(%ebp), %edx movl %eax, (%ecx) #赋值给传入地址的调用者的变量 movl %edx, 4(%ecx) movl %ecx, %eax leave ret $4 .LC0: .string "%c\n" .globl main .type main, @function main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $36, %esp #为什么是36呢? main函数的int argc, char *argv[]8个字节, #struct A t 局部变量8个字节,然后add()调用12个字节--其中有一个是隐式的入栈,%eax记录 #局部变量的地址, 然后printf8个字节 leal -16(%ebp), %eax movl $4, 8(%esp) #参数入栈,这儿入栈没有用pushl,而是先subl esp,预留好空间,然后movl,是等效的 movl $3, 4(%esp) movl %eax, (%esp) #需要赋值的局部变量的地址入栈,有了这个地址,赋值工作就在add()zh中完成了 call add subl $4, %esp movzbl -12(%ebp), %eax movsbl %al,%edx movl $.LC0, %eax movl %edx, 4(%esp) movl %eax, (%esp) call printf movl $0, %eax movl -4(%ebp), %ecx leave leal -4(%ecx), %esp ret
好了,基本上解决问题了,知道碰到struct A function();的赋值问题怎么解决了, 但还有个问题, 如果没有赋值,我只是调用add(3, 4);
那调用者的%eax,应该把什么地址入栈呢?
经过实验法现,不管你有没i有var = add(3,4); 编译器都回在调用者的栈上预留一个局部变量的空间,然后把这个变量地址 -> eax -> pushl stack
恩,这些基本上算是没啥问题了~~:)