汇编中函数返回结构体的方法

代码生成,函数的返回值是个问题,如果返回值是简单类型,如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

恩,这些基本上算是没啥问题了~~:)

转载于:https://www.cnblogs.com/bluesea147/archive/2012/07/15/2592840.html

上一篇:2019-2020-1 20199310《Linux内核原理与分析》第二周作业


下一篇:编译器C-Free V352注册算法分析