C/CPP, 编程

探讨C语言返回struct的实现

C语言基本类型的返回通过寄存器实现。

寄存器容量不大,一个结构体的大小不定,多塞几个成员,很容易就会超过寄存器的容量,不太可能通过寄存器一次性返回一个结构体。

把以下代码转为汇编,探个究竟:

// return_struct.c

struct the_struct {
        int num1;
        int num2;
        double num3;
        double num4;
        int *num_ptr1;
        int *num_ptr2;
};

struct the_struct fun() {
        struct the_struct the_struct1;
        the_struct1.num1 = 1;
        the_struct1.num2 = 2;
        return the_struct1;
}

int get_struct() {
        struct the_struct the_struct1 = fun();
        return the_struct1.num1 + 1;
}

优化后的代码可能比较晦涩,64位寄存器数量是32位的一倍,为了简便理解,关闭代码优化,并且转为32位汇编,命令:

bash # gcc -O0 -S -masm=intel -m32 return_struct.c

结果:

        .file   "return_struct.c"
        .intel_syntax noprefix
        .text
        .globl  _fun
        .def    _fun;   .scl    2;      .type   32;     .endef
_fun:
        push    ebp
        mov     ebp, esp
        sub     esp, 32 ; 为结构体在栈帧中分配32字节内存
        mov     DWORD PTR [ebp-32], 1 ; *(ebp - 32) = 1
        mov     DWORD PTR [ebp-28], 2 ; *(ebp - 28) = 1
        ; ebp指向的位置储存着get_struct()函数的ebp值
        ; ebp+4指向返回地址
        ; ebp+8是get_struct()函数中结构体的指针
        mov     eax, DWORD PTR [ebp+8] ; eax = get_struct()函数中结构体的指针
        mov     edx, DWORD PTR [ebp-32] ; edx是fun()函数中结构体首DWORD的值
        mov     DWORD PTR [eax], edx ; *eax = edx,让get_struct()中的结构体第一个DWORD = edx
        ; 结构体剩余的其余值传值
        mov     edx, DWORD PTR [ebp-28]
        mov     DWORD PTR [eax+4], edx
        mov     edx, DWORD PTR [ebp-24]
        mov     DWORD PTR [eax+8], edx
        mov     edx, DWORD PTR [ebp-20]
        mov     DWORD PTR [eax+12], edx
        mov     edx, DWORD PTR [ebp-16]
        mov     DWORD PTR [eax+16], edx
        mov     edx, DWORD PTR [ebp-12]
        mov     DWORD PTR [eax+20], edx
        mov     edx, DWORD PTR [ebp-8]
        mov     DWORD PTR [eax+24], edx
        mov     edx, DWORD PTR [ebp-4]
        mov     DWORD PTR [eax+28], edx
        mov     eax, DWORD PTR [ebp+8]
        leave
        ret     4
        .globl  _get_struct
        .def    _get_struct;    .scl    2;      .type   32;     .endef
_get_struct:
        push    ebp
        mov     ebp, esp
        sub     esp, 40 ; 分配40字节内存
        lea     eax, [ebp-32] ; eax = ebp - 32,eax是结构体的指针
        mov     DWORD PTR [esp], eax ; *esp储存结构体的指针,即get_struct()函数栈顶是结构体的指针
        call    _fun
        sub     esp, 4
        mov     eax, DWORD PTR [ebp-32] ; (ebp - 32)是结构体的地址
        add     eax, 1
        leave
        ret
        .ident  "GCC: (GNU) 5.3.0"

汇编代码已写上注释。

在get_struct()函数中,调用fun()之前,会首先把get_struct()函数中要接收返回值的地址压入栈中。

在fun()中,要返回值时,直接从get_struct()的栈帧取得保存结构体值的地址,直接对get_struct()的栈帧进行操作。

总的来说,一个函数要返回一个结构体,则从调用者函数的栈帧中取得接收结构体的地址(被赋值结构体的指针),直接对调用者函数的栈帧进行修改。

101 Posts

自信、努力、活出精彩;以前未所见的颜色,绘大千世界!
View all posts

2 thoughts on “探讨C语言返回struct的实现”

Leave a reply

Your email address will not be published. Required fields are marked *