C语言函数栈帧详解

系列文章目录


前言

最近正在学习栈帧方面的知识,由于本人对汇编不太熟悉,对其中频繁出现的ESP寄存器和EBP寄存器一直没搞清楚,在查找资料后,在此进行整理,方便以后温故知新。


一、预备知识

要清楚理解栈帧的概念,首先我们要明白内存分区和栈的概念。

1.内存分区

按照内存地址从高(0xffffffff)到低(0x00000000)的顺序排列,可分为5大分区:栈区 -> 堆区 -> 全局静态区 -> 常量区 -> 代码区。大致分布如下图所示,
C语言函数栈帧详解_第1张图片
栈:函数在调用过程中,会在内存中开辟一块名为栈帧的空间,用于存放局部变量等数据。

2.什么是栈?

栈是一种数据结构,有两个特点:1.只能从一端插入或删除数据。2.先进后出。

二、栈帧是什么?

C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。(来自百度百科)。C语言函数栈帧详解_第2张图片

1.ESP寄存器是什么?

ESP即Extended stack pointer的缩写,直译过来就是扩展的栈指针寄存器。之前学的计算机组成原理中提到的堆栈寻址就是使用SP寄存器存放操作数的地址。那么,ESP和SP有什么区别呢?显而易见,ESP是32位的,SP是16位的,存放的都是栈顶地址(或指针)。总之,该指针总指向栈的顶部(低地址)。

2.EBP寄存器是什么?

EBP即Extended base pointer的缩写,直译过来就是扩展的基址指针寄存器。该指针总是指向当前栈帧的底部(高地址)。

三、详解栈帧创建与销毁全过程

1.测试程序

我这里使用了compile explorer x86-64 gcc 11.2,这里的rsp和rbp可以分别看作是64位的esp和ebp。

代码如下:

int  add(int a,int b)
{
    int c = 0;
    c = a + b;
    return c;
}
int main()
{
    int a=0;
    int b=1;
    int sum=0;
    sum=add(a,b);
    return 0;
}

生成的汇编代码如下:

add(int, int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-20], edi
        mov     DWORD PTR [rbp-24], esi
        mov     DWORD PTR [rbp-4], 0
        mov     edx, DWORD PTR [rbp-20]
        mov     eax, DWORD PTR [rbp-24]
        add     eax, edx
        mov     DWORD PTR [rbp-4], eax
        mov     eax, DWORD PTR [rbp-4]
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 0
        mov     DWORD PTR [rbp-8], 1
        mov     DWORD PTR [rbp-12], 0
        mov     edx, DWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, edx
        mov     edi, eax
        call    add(int, int)
        mov     DWORD PTR [rbp-12], eax
        mov     eax, 0
        leave
        ret

2.分析汇编代码

假设执行main函数前,栈空间如图所示:C语言函数栈帧详解_第3张图片
下面逐一执行指令:

push rbp
#将rbp寄存器中的地址压入栈中。

mov rbp, rsp
#将rsp的值赋值给rbp

sub rsp, 16
#rsp的地址减16

mov DWORD PTR [rbp-4], 0
mov DWORD PTR [rbp-8], 1
mov DWORD PTR [rbp-12], 0
#rbp-4指向的内存单元存放0
#rbp-8指向的内存单元存放1
#rbp-12指向的内存单元存放0

mov edx, DWORD PTR [rbp-8]
mov eax, DWORD PTR [rbp-4]
mov esi, edx
mov edi, eax
#1存放到edx和esi,0存放到eax和edi

call add(int, int)
调用add函数

push rbp
mov rbp, rsp
将此时rbp的值压入栈中,rsp的值赋给rbp

mov DWORD PTR [rbp-20], edi
mov DWORD PTR [rbp-24], esi
mov DWORD PTR [rbp-4], 0

mov edx, DWORD PTR [rbp-20]
mov eax, DWORD PTR [rbp-24]
add eax, edx
#将0和1送入累加寄存器进行加法运算,结果写回eax

mov DWORD PTR [rbp-4], eax
mov eax, DWORD PTR [rbp-4]

pop rbp
#rbp出栈
ret
#返回,此时add函数栈帧销毁


总结

以上是对栈帧的总结。

你可能感兴趣的:(c语言)