前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >函数栈帧的创建和销毁介绍

函数栈帧的创建和销毁介绍

作者头像
用户11039545
发布2024-03-28 17:25:07
910
发布2024-03-28 17:25:07
举报
文章被收录于专栏:c语言c语言

首先理解一下寄存器:

eax,ebx,ecx,edx,ebp,esp。画横线的这两个寄存器存放的是地址。这两个地址是用来维护函数栈帧的。

每一次函数调用,都要在栈区创立一个空间。

什么是栈?

函数通过栈来实现控制转移、参数传递、局部变量的分配和释放3个功能。 计算机有专门的一块内存区域作为栈,每个函数都可以在栈上申请一块内存区域作为函数的存储空间,而该存储空间则被称为函数的栈帧。

栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push),也可 以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈

编写代码

详细解释栈帧创立和销毁过程

如下图所示,在栈区(计算机专门的内存空间),每个函数在栈区申请一块内存空间,称为函数栈帧。在调用哪个函数,esp和ebp就跑去维护哪个函数的栈帧。我们通常把ebp称为栈底指针,esp称为栈顶指针。栈区的使用情况是先使用高地址,再使用低地址(向下增长)。

点调试窗口。按下图进行操作。

我们在函数执行完的时候,可以在调用堆栈中看到:

main函数被__tmainCRTStartuo()调用。

而上面的函数又被上图中下面一个函数调用。

esp和ebp首先维护main函数,再调用add函数。

此时ebp,esp维护的是这样一个空间。进入main函数的第一步是push。push压栈,栈里面放的是ebp。当push完成之后,esp指到栈顶。

我们也可以在监视中观察到:1

2

我们可以观察到esp的地址 减少了4,意味着esp往上了4位。

接着执行move,move是把esp的值给ebp。那么ebp就不再指向原来的地方了。

同样的,也可以在监视窗口中看到

接着执行sub,给esp减去0E4h(16进制数字)。意味着esp指向上面的某一块位置。

这块空间就是为main函数申请的空间。紧接着,push三次。顶上压了个元素ebx,push完之后esp向上指。执行完成之后,在顶上压了三个元素。

lea:load effective address 加载有效地址

接着,lea,ebp-0E4h

接着执行到rep stos,把39h里的双字(4个字节)全部改为eax的内容0CCCCCCCCh 。

到ebp结束。往上所有的空间都初始化为cccccccc。

为啥不初始化打印出来的就是烫烫烫烫呢?

是因为main函数调用时,在栈区开辟的空间的其中每一 个字节都被初始化为0xCC,而arr数组是一个未初始化的数组,恰好在这块空间上创建的,0xCCCC(两 个连续排列的0xCC)的汉字编码就是“烫”,所以0xCCCC被当作文本就是“烫”。

接着执行的语句让ebp-8中放入了一个10。(与上一个数值之间差了两个整型)

接着调用add函数,

接着在栈顶压20。

mov 把ebp-14h放到eax里。也就是把20的值放到eax里面去了,然后push eax,eax压栈,里面存放的是20。然后move,把 ebp-8的值放到了ecx当中。push,顶上又压了一个ecx10。

接下来call指令调用函数,按F11。call指令又把add函数的地址压到顶上来了。(call指令的下一条指令的地址)

然后来到了add函数

前面这一堆和原先的函数内容一样。

sub,给esp减去一个0CCh

然后又是三次push。edi到ebp之间的所有空间全部初始化为CCCCCCh。

ebp+8

ebp+8把a撇b撇找过来了。把ebp+8的值加到 eax里,再把ebp+12的值加到eax里。再把算出的结果30放到ebp-8里面去。我们可以发现参数是从右向左传的。形参不是在add函数内部创建的,而是找到刚刚传参压过去的空间。a和b就会分别被认为是x和y。在没有调用add函数时,参数就已经传过去。我们可以说,形参是实参的一份临时拷贝。改变形参a撇b撇,不改变实参。

最后一步return z,z是怎么返回的呢?把ebp-8的值放到eax里面去。eax是个寄存器,寄存器是不会退出就销毁的。ebp-8就是z,里面存放着30的值。等回到主函数时,再把eax的值拿出来用就行了。

三次pop弹出

再把ebp赋给esp。然后再pop一下,把栈顶的元素弹出来,栈顶弹的是main函数的ebp。ebp的地址存在main函数当中,就是要让随着函数调用返回之后,随着栈帧的销毁,栈顶是很容易找到的,但是栈底不容易找到。pop弹出,ebp走了。

ebp就回回去了。pop一下找到了main函数的栈帧空间。

这样就顺顺利利地回到了main函数里头了,还应该从call指令的下一条指令执行。顶上放着call指令下一条指令的地址,在栈顶存这个地址就是为了在函数调用完之后还能回来,回来之后还能从call指令的下一条指令往下执行。在函数调用完成之后,形参x和y就没有作用了

ebp+8。esp指向移动8个字节,上面的空间不属于。再把eax的值放到ebp-20h当中。eax的值就是出add函数时委托到eax当中的和,和放到局部变量c当中,这样返回值就带回来了。

解决疑惑

局部变量是如何创建的?

首先为函数分配好栈帧空间,栈帧空间初始化好一部分空间之后,然后给局部变量在栈帧里分配一点空间。

为什么局部变量不初始化内容是随机的?

随机值是被随机放入的。如果初始化,就相当于把随机值覆盖了。

函数调用时参数时如何传递的?

当没有调用函数的时候已经pushpush把两个参数从右向左开始压栈压进去了,当真的进入形参函数的时候,其实在add函数栈帧里,通过指针的偏移量找回了形参。

函数的返回值是如何带会的?

调用之前就把call指令的下一条指令的地址记住了,当往回返的时候,就可以跳转到call指令下一条指令的地址,返回值是通过寄存器的方式调用回来的。

欢迎交流!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-01-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是栈?
  • 编写代码
  • 详细解释栈帧创立和销毁过程
  • 解决疑惑
    • 局部变量是如何创建的?
      • 为什么局部变量不初始化内容是随机的?
        • 函数调用时参数时如何传递的?
          • 函数的返回值是如何带会的?
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档