函数栈帧的创建与销毁(通俗且详细)

目录

一:寄存器:

二:__tmainCRTStartup函数与mainCRTSartup函数

三:接下来是main函数栈帧的创建

四:接下来开始在main函数里面初始化局部变量

五:main函数中函数参数的传递

六:创建add函数的栈帧,并进行局部变量的初始化 

七:返回函数值

八:解决几个常见的问题

question1:

question2:

question3:

question4:

question5:

question6:


一:寄存器:

eax ; ebx ; ecx edx

ebp ; esp

ebp 与 esp这两个寄存器中存放的是两个地址,这两个地址是用来维护函数栈帧的.

ebp:又叫栈底指针,主要是存放函数栈帧的底部地址

esp:又叫栈顶指针,主要是存放函数栈帧顶部的地址

 

栈中数据存放的特点:从高地址向低地址存放数据

寄存器ebp存放的是当前函数栈底的地址

寄存器esp存放的是当前函数栈顶的地址

当调用另一个函数的时候,esp与ebp也会相应的改变,esp去存放被调函数的栈顶的地址,ebp去存放被调函数的栈底的地址。

二:__tmainCRTStartup函数与mainCRTSartup函数

每一次函数的调用,都要在栈上开辟空间,在main函数中调用add函数,就会在栈上为add函数开辟一块空间,调用main函数时,也会为main函数开辟一块空间。

那谁调用的main函数呢?是__tmainCRTStartup这个函数调用的main函数,那又是谁调用的__tmainCRTStartup函数呢?是mainCRTSartup函数调用的__tmainCRTStartup。

 栈的使用特点是:由高地址向低地址

三:接下来是main函数栈帧的创建

 

 说明:

压栈(push):向栈里存放数据(将数据存放在栈顶)

出栈(pop):从栈里删除数据(将数据从栈顶删除)

第一步是push  ebp  压栈,将ebp压入栈中,esp也相应的自动向上移动,存放

栈顶的地址   这里(蓝括号里面)存放的是__tmainCRTStartup函数栈底的地址

之所以要将ebp(__tmainCRTStartup函数栈底的地址)压入栈顶,其目的是:当main函数调用完回来以后

可以找到__tmainCRTStartup函数的栈底,因为调用完main函数回来很容易找到__tmainCRTStartup函数

的栈顶,而栈底是比较难找的,所以干脆把栈底的地址给放到函数栈帧的上部,方便,main函数回来后

寻找栈底。

第二步是mov  ebp esp意思是将esp的值赋值给ebp,即将__tmainCRTStartup函数栈顶的地址赋值给ebp,现在ebp指向__tmainCRTStartup函数栈顶

这是准备创建main函数的动作-》移动esp与ebp

第三步是sub  esp 0e4h意思是esp-oe4h,esp向上移动0e4h个类型单元

456步是push ebx,push esi,push edi

esp向上移动3个类型单元

第七步是 lea   edi  ebp+0e4h

lea的意思是:load effective address  加载有效地址

这里 lea   edi  ebp+0e4h的意思是:将ebp+0e4h赋值给edi

将这个位置的地址存放在edi里面

8步是 mov ecx  39h意思是:将39h这个数赋值给ecx,

ecx里面存放着39h这个数值

9步是mov  eax 0CCCCCCCCh意思是,将0CCCCCCCCh这个数赋值给eax

eax里面现在存着0CCCCCCCCh数值

10

edi这个位置开始,向下esi个类型单元,都赋值成为ebx

即从A位置开始向下39h个类型单元,每个类型单元里面的数值都被赋值成为 0CCCCCCCCh

 

这些动作完成之后main函数的栈帧被创建出来

四:接下来开始在main函数里面初始化局部变量

11

意思是将0A10)存放在ebp-8这个地方

这是赋了初值,如果没有赋初值,那么ebp-8这个地方存放的是cccccccc,所以打印的是一个随机数

 

12

这里的意思是:将14h20)存放在ebp-14h这个地方

00)存放在ebp-20h这个地方

函数局部变量的创建方式:

首先先创建好函数的栈帧

然后在栈帧里面存放函数的局部变量

五:main函数中函数参数的传递

13141516

分别是:将ebp-14h里的内容(b==20)赋值给eax

eax压入栈中

ebp-8里面的内容(a==10)赋值给ecx

ecx压入栈中

之后esp向上移动两个类型单元

 

17,18,19

call指令是调用函数,走到call的时候,会进入add函数内部,对add函数再开辟栈帧,然后存储add函数内的局部变量,但是在call的同时,会将call下面的语句的地址压到栈顶,因为在调用完add函数后会返回来继续往下执行,所以要记住下一行的地址。

六:创建add函数的栈帧,并进行局部变量的初始化 

20步,通过call的函数调用,进入add函数

 

创建add函数的栈帧,就不写了,其与main函数的栈帧的创建相同

创建add函数的局部变量:将0赋值给ebp-8

ebp+8的内容(10)赋值给eax=10

Add     eax,dword  ptr  [ebp+0ch]    的意思是:向eax里面再加上 [ebp+0ch]

就相当于eax=eax+ [ebp+0ch] ==30

因为 [ebp+0ch] = [ebp+12]=20

再将eax的值赋值给ebp-8

注意这里函数参数的传递,并没有在add函数里面创建参数,而是使用的main函数压栈的参数,

即将main函数里面的参数(要传的参数)压在main函数的栈的上部,方便被调函数获取

注意:参数调用的先后顺序是先a,b

add  (  a  ,  b  )先左后右

之前说的形参是实参的临时拷贝,这句话是完全正确的,从内存图中就可以清楚的看到。

而且形参的变化不会影响实参

七:返回函数值

21步,开始return z

首先将ebp-8的内容赋值给eax,注意eax是一个寄存器,他不会随着栈帧的销毁而消失,所以这个操作是很安全的。等跳出add函数后,add函数的栈帧就会被销毁,再将eax里的内容拿出来,赋值。

之后接连着3pop,弹出栈顶的值,并将其赋值给edi,再弹出栈顶的值,并将其赋值给esi,再再弹出栈顶的值,并将其赋值给ebx,

之后将ebp赋值给esp,这样esp瞬间来到ebp的位置,最会pop 栈顶的内容赋值给ebp;

因为栈顶存放的内容是ebp-main函数栈底的地址(解释一下为什么栈顶存放的内容是ebp-main函数栈底的地址,因为add函数调用完回来以后,很容易找到main函数的栈顶,但是栈底是很难找到的,所以先把栈底的地址保存一份存放起来,方便后续函数调用完回来找到栈底),所以ebp又瞬间的指向了main函数的栈底

esp也向下移动一个类型单元

从而完成了add函数栈帧的销毁

最后:ret其实就是弹出栈顶的call指令下一条指令的地址;然后跳到那里去。

进而esp再向下移动一个类型单元

进入ret,即进入main函数,进而在main函数内操作

(call是进入函数,ret是出函数)

跳到了call指令的下一个指令,这就解释了为什么将call指令的下一条指令的地址放在栈的上部;

是回到main函数以后,还能再继续执行函数调用完以后的语句。

执行add    esp8意思是esp=esp+8

现在就销毁里两个形参

之后再执行mov  dword ptr  [ebp-20h],eax即将eax里面的值赋值给ebp-20h

函数的返回值是如何返回的,首先是先将要返回的内容存放再eax寄存器中,然后回来的时候再将eax里面的内容赋值给接收的变量

八:解决几个常见的问题

 

question1:

 局部变量的创建:首先要先创建好函数的栈帧,然后在函数栈帧的基础上进行局部变量的创建。局部变量的创建是从函数的栈底开始往上创建的

每个局部变量创建的类型单元之间的距离间隔可能不同。

question2:

如果局部变量不初始化的化,那么内存里面存的是CCCCCCCCC这种编译器自己生成的东西,那么打印出来的值肯定是随机值

question3:

函数的传参并非在函数里面开辟空间存放形参,举个例子:

在main函数内部要向add函数传递形参add(a,b),

形参a,b是存放在main函数的栈帧的上部的,先存b,后存a.

然后add函数使用形参的时候,直接向下通过ebp来拿就可以了。

question4:

形参是实参的一分临时拷贝,这句话一点毛病都没有,通过内存就可以看到,实参与形参分别占有不同的内存空间,形参的改变不会影响实参。

question5:

将形参放在main函数的上部后,进行call指令,call指令进行的同时,也会将call指令的下一条指令压入栈顶。

call指令主要的是函数的调用,通过call指令调用add函数,

如何调用add函数呢?首先创建add函数的栈帧,然后再创建局部变量。

question6:

函数调用完成以后,首先将返回值存放在寄存器中(安全),然后将esp与ebp返回到main函数之前的位置(具体如何返回上面写的很详细)

最后ret跳出add函数,ret就是弹出栈顶的call指令的下一条指令的地址,然后跳到那里去。

所以就跳回到main函数指令中.继续接着call指令后面往后执行。

 把每一件简单的事做好,就不简单了;把每一件平凡的事完成,就不平凡了! 

与君共勉

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chen森柏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值