函数栈帧的创建和销毁(vs2013)

本文深入解析了函数调用过程中的栈帧创建、销毁,以及局部变量的存储、传参机制。通过示例程序,详细阐述了从main函数到子函数add的调用过程,包括esp和ebp寄存器的作用,以及栈空间的变化。此外,还讨论了形参与实参的关系以及函数返回值的处理。
摘要由CSDN通过智能技术生成

1.寄存器

eax、ebx、ecx、edx    { esp、ebp }(这两个重点,存放的是地址,这两个地址是用来维护函数栈帧的。)

2.每一个函数调用,都要(在栈区上)创建一个空间

接下来,用下面简单程序来向大家解释函数的栈帧的创建与销毁

现在mian函数被调用,所以要在栈区创建一个空间

 

 这时我们按下F10 进入调试,点击上方 调试 空能选择 窗口 - 调用堆栈 

 一直按F10 知道出现以下界面

 这里表示main函数在vs2013中是被调用的

 那么是谁调用了main函数呢,往上翻,你会发现

 

 所以main函数是在__tmainCRTstartup函数中被调用,而__tmainCRTstartup函数是被mainCRTstartup调用的。

即 main --------> __tmainCRTstartup  -------> mainCRTstartup

所以,有这么个东西的,在调用main函数之前,也要为它们准备空间

以上就是大家讲一下,栈空间存储模式。

接下来我们开始详细解释 函数的创建与销毁。

首先我们回到主界面,按下 F10 进入调试,直接反键,选择转到反汇编

 这样我们就进入了C语言的汇编代码了

 接下来 我们来理解这些代码的意思

首先设置成如下情况

 方便我们一会通过地址来解释。

现在大家注意,我们马上要调用mian函数了

然后我进入 main函数 

 它的第一步 是push sbp     /    ebp(压栈操作),pop(出栈操作)

// 压栈:给栈顶放一个元素, 出栈从栈顶删除一个元素

意思就是说想栈压了一个数据

这是没有进行压栈之前

  当压了一个数据ebp之后,它成了栈顶,所以esp栈顶指针,要指向它了

 你会发现xsp的值变量,是因为压栈使用习惯是先高后低,也就意味地址减去4个字节。

这时我们通过调式 - 内存会发现,ebp的值确实压进了esp

 接下来第二条语句 mov,mov在这里的意思就是把esp的值赋给ebp

即ebp不再指向下面位置,而是跟esp指向同一个位置

 

我再通过监视发现,刚才我想法是对的。

 

 下面就是第三条语句

 

 意思为esp减去一个十六进制数 0E4h

即减去 288

esp值就变成了

 意味着esp的地址变小了,esp不再指向原来的位置,如下图:

 接下来 就三条push语句,意思就是给顶上压进三个元素

 

同时 esp要指向栈顶edi

 跟监视和内存也证明了这一观点,esp确实把这三个数压进了。

如图:

 

 

 然后就是下一条语句

 lea  等价于  load effective address(加载有效地址)

即 把 有效地址  [ebp+FFFFFF1Ch]  加载到 edi里面去。

我把显示符号位勾选上,这样方便你们理解

edi减去 0E4h  再根据上文

 等于就是说

 让ebp指向了箭头位置

 然后接下来三条语句

ecx。39h 意思就是:把 39h 放ecx里面去

eax.0CCCCCCCCh        /               eax = 0CCCCCCCCh

 最后一句的意思是,把从edi这个位置开始,向下的 39h次的数据,全部都改成0CCCCCCCCh

(也就是吧main函数初始化)

dword 的意思double word ,4个字节,也就是说每次操作4个字节

 

 

 到目前为止,我们的main函数栈空间就开辟好了。

接下来就轮到我们 a 和 b ,c了(再取消勾选符号名)

 意思就 a 放在地址 ebp-8 的位置上, b放在地址 ebp-14h位置上,

c放在地址 ebp - 20h

( 注意c的位置不是最后一个栈空间。只是画的不够大。)

 

 接下来就函数调用(传参)

 

 第一句 mov 把 ebp-14h的值赋给eax,就是先把b的值传过去,eax=20

 

然后它要push eax 

即 

 然后把ebp-14h的值赋给ecx,即   ecx =10,然在再压栈

 (注意 现在 esp指向ecx栈顶)

以上两个动作就是在传参。

 就下来就是重要 的 call 这条语句  call(调用函数的意思/另外记住call的地址)

到这里 想看得更仔细一点 按F11

 按下F11之后

 你会发现call里面放的是下一条指令的地址,也就是压栈了地址

 为什么要这么做呢?,因为call 去调用add函数,调完了,你要回来。你要回到call语句的下一条指令的位置,然后向下执行,而恰好把call语句下一条指令记住了,放在这。一会回来的时候,我们就能用上,回来的时候我们就会找到这个地址,往下继续执行

然后我们按下 F11,进入如下界面

 这时候我们真正来到add函数内部,你会发现跟为main准备函数空间一样。这里我就不再赘述。不然不懂,可以参考前面的过程。

 由此可见 z =30的

接下来就是返回参数

这句话的意思就是 把 ebp-8 (c=30),存入寄存器 eax中,应该局部变量用完是要销毁的,所以需要先存起来。 到后面要用的时候再拿出来

然后  三pop语句,(出栈操作)

 结果如下:

 接下来 mov语句,把 ebp的值赋给esp

 

 

 

 

 这里相当于吧 a和b的空间也还给操作系统了。

 这里吧 eax的值(z= 30)赋给 ebp - 20h 即赋给c,故 c = eax =30

最后程序的结果显而易见 打印 30

总结

本文从内存层面上主要讲解函数的以下几个问题

  • 局部变量是怎样创建的

首先我们给这个函数分配号栈帧空间,栈帧空间初识化空间之后,然后给我局部变量分配一定的空间

  • 为什么局部变量的值是随机值

因为随机值使我们放进去的 

  • 函数是怎样传参的,传参的顺序是怎样的

当我们要去调用函数的时候,我们已经push push 把这两参数 a,b,从右向左开始压栈,压进去。当我们真正进入函数时,add的函数栈帧通过指针的偏移量,找到我们的形参

  • 形参和实参之间是什么关系

形参是实参的一份临时拷贝,改变形参,不会改变实参。

  • 函数调用是怎么做的

我们在调用之前我就把call指令,下一步指令记住了,压进去了,把ebp调用这个函数的上一个函数的栈帧的ebp存进去了,当我们函数调用完了,返回的时候,弹出(pop)ebp,就能找原始上一个函数调用的ebp,然后指针往下走的时候就能找到esp的顶,然后回到栈帧空间,然后就是我们记住call下一步的命令的地址,当我们往回返的时候,就可以跳到call的下一步的指令的地址,让我们函数调用完了之后,可以返回,

  • 函数调用结果如何返回

返回的结果是通过寄存器返回得到的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Dark And Grey

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

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

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

打赏作者

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

抵扣说明:

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

余额充值