当前位置 : 主页 > 编程语言 > c语言 >

栈帧的创建与销毁

来源:互联网 收集:自由互联 发布时间:2023-09-03
在学习函数栈帧的创建与销毁前,我们应该理解寄存器的概念作为铺垫:esp ebp eax ebx ecx(本质是一个计数器) edx等寄存器,其中esp ebp尤为重要,这是因为这两个寄存器中存放的是地址,而


在学习函数栈帧的创建与销毁前,我们应该理解寄存器的概念作为铺垫:esp ebp eax ebx ecx(本质是一个计数器) edx等寄存器,其中esp ebp尤为重要,这是因为这两个寄存器中存放的是地址,而这两个地址是用来维护栈帧的

0.ebp和esp是如何来维护栈帧的呢?

当我们调用Main函数时

栈帧的创建与销毁_寄存器

栈帧的创建与销毁_栈帧_02


当我们调用Add函数时

栈帧的创建与销毁_寄存器_03

栈帧的创建与销毁_寄存器_04


注:通常我们成ebp为栈底指针,esp为栈顶指针,此外在32位环境下是ebp和esp而到了64位环境下栈底指针和栈顶指针变为rbp和rsp

当我们弄懂了寄存器的概念,此时我们就正式开始学习函数的栈帧和创建,在学习前,带着疑问,进入学习:

1.为什么局部变量的值不初始化是随机的?

2.局部变量是怎么创建的?

3.函数是如何传参的?传参的顺序是怎样的?

4..函数是如何调用的?

5.形参和实参的关系?

6.函数调用结束后如何返回?

1.为什么局部变量的值不初始化是随机的?

其他编辑器调用过程过于繁琐,不利于我们观察堆栈的调用过程,为了方便观察和学习,我们以VS2013为例,查看函数在堆栈中的调用情况

栈帧的创建与销毁_C语言_05

栈帧的创建与销毁_栈帧_06


通过该调用过程,我们不难看出,在调用main函数之前,我们首先调用了mainCRTStartup函数,通过mainCRTStartup函数调用了__tmainCRTStartup函数调用了main函数

栈帧的创建与销毁_栈帧_07

栈帧的创建与销毁_C语言_08


我们使用ebp(rbp)和esp(rsp)来维护main函数的栈帧空间,首先进行压栈,此时esp栈顶指针的位置上移,而后将esp的地址赋给ebp,使得ebp指向了esp

栈帧的创建与销毁_寄存器_09

栈帧的创建与销毁_C语言_10


而后我们开辟出了一块大为0E4h大小的栈帧空间,该空间即为main函数的栈帧空间,esp随着栈帧空间的开辟也随之发生了改变

栈帧的创建与销毁_寄存器_11

栈帧的创建与销毁_栈帧_12


而后我们再对其进行压栈,esp(栈顶指针)依旧随之发生改变

栈帧的创建与销毁_C语言_13

栈帧的创建与销毁_C语言_14


通过这步操作,我们将ebp-24h的空间地址存储到edi中,也就是main函数的栈帧空间地址,往下看发现,其实该步真正起作用的地方为rep,它的意思就是,从edi开始,ecx次将eax的内容初始化为CCCCCCCC

栈帧的创建与销毁_栈帧_15

栈帧的创建与销毁_C语言_16


其实在这一步就证明了,为什么我们不对新创建的变量进行初始化,而其内容为随机值,就是因为其默认初始值为CCCCCCCC

2.局部变量是怎么创建的?

我们将0Ah的内容放入ebp-8中,其实这0Ah所代表的就是变量a的值10,将14h的值放入epb-14h地址处(图像大小有限,我们很难准确画出,图中画出的只是大致范围)

栈帧的创建与销毁_寄存器_17

栈帧的创建与销毁_寄存器_18

3 .函数是如何传参的?传参的顺序是怎样的

传参时我们将ebp-14h和ebp-8的地址分别传给eax和ecx寄存器,并进行压栈,esp的地址也随之发生改变

栈帧的创建与销毁_寄存器_19

栈帧的创建与销毁_栈帧_20

 我们通过该步骤调用了call命令,并将该地址保存在栈帧空间内部,esp再次发生改变,维护函数空间

栈帧的创建与销毁_C语言_21

栈帧的创建与销毁_C语言_22


4.函数是如何调用的

通过该步骤,我们进行操作,将main函数的栈底进行压栈,esp的随栈帧空间发生改变,将esp的地址赋给ebp,我们开辟一块0CCh大小的空间,用来维护add函数的栈帧,对ebx,esi,edi等寄存器进行压栈,将epb-0Ch处的地址加载到edi中,同时初始化add函数栈帧空间的内容为0CCCCCCCCCh

栈帧的创建与销毁_栈帧_23

5.形参和实参的关系

我们常说形参其实是实参的临时拷贝,改变形参其实是无法改变实参的,具体原因是怎样的?

该步骤,寻找到ebp-8的位置开辟变量Z的栈帧,将其初始化为0,将ebp+8和ebp+12的内容相加得出了x+y的和,其实我们这一步不难发现,我们在main函数中调用add函数进行传参,其实仅仅只是将实参的值放到两个寄存器内,进行压栈,我们在add函数中调用形参时,访问的其实是寄存器中的内容,这也是为什么,我们常说,形参是实参的临时拷贝,修改形参无法对实参造成任何影响

栈帧的创建与销毁_栈帧_24

栈帧的创建与销毁_栈帧_25

6.函数调用结束后如何返回?

当调用结束后,将ebp-8的内容存储到eax寄存器中,同时将edi esi ebx出栈,此时esp维护空间也发生了变化,esp+00Ch,此时销毁了add函数的栈帧空间,将ebp的地址赋给esp,但是我们在main函数中留有call函数的地址,将其弹出,回头main函数中,同时esp+8,栈顶指针再次发生改变,同时将eax中add函数的返回值放到ebp-20中,此时才是真正意义的返回

栈帧的创建与销毁_栈帧_26

栈帧的创建与销毁_C语言_27


上一篇:【Linux】Linux常见指令
下一篇:没有了
网友评论