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

你知道函数栈帧的创建和销毁吗?

作者头像
南桥
发布2024-01-26 17:37:51
1160
发布2024-01-26 17:37:51
举报
文章被收录于专栏:南桥谈编程南桥谈编程
在这里插入图片描述
在这里插入图片描述

前言

在前面的学习中,可能会出现许多疑惑: 1、局部变量是怎么创建的? 2、函数是怎么传参的? 3、函数调用是怎么做到的? 4、函数调用结束后是怎么返回的? … 希望读者在看完小编的文章,对一系列问题会有所掌握

观图有感

你去野外烧烤,并为此创建了一个待办事项清单——一叠便条。

在这里插入图片描述
在这里插入图片描述

将想到的烧烤食物写在便条上,一个食材一个便条,最先想到的食材写在便条上后,放在最下面,依次往上放,最后想到的写在便条上后,放在最上面。

之后,在烧烤的时候,从上往下拿,拿出来的表示你已经在烧烤了,可以将它删去。

一叠便条要简单得多:插入的待办事项放在清单的最前面;读取待办事项时,你只读取最上面的那个,并将其删除。因此这个待办事项清单只有两种操作:压入(插入)和弹出(删除并读取)。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这种数据结构称为栈。栈是一种简单的数据结构,之前学函数的时候我们一直在使用它,却没有意识到!

一、概述

函数栈帧是在内存中的栈区为被调函数开辟的一块空间,里面用来存放该函数中定义的变量等东西,当函数运行完毕栈帧将被销毁。

可以想象成洗盘子,最先吃完的人将盘子放在最下面,后面吃完的人依次将盘子叠放在前一个的上面。于是,最后吃完的人的盘子就在最上面,也就是最先洗。

Push(入栈):为栈增加一个元素 Pop (出栈): 从栈中取出一个元素

二、寄存器

寄存器是中央处理器内用来暂存指令、数据和地址的电脑存储器。寄存器的存贮容量有限,读写速度非常快。在计算机体系结构里,寄存器存储在已知时间点所作计算的中间结果,通过快速地访问数据来加速计算机程序的执行。 –百科

Name

Function

eax

“累加器”, 用来存放函数的返回值

ebx

"基地址"寄存器,可作为储存器指针来使用, 在内存寻址时存放基地址

ecx

计数器, 在循环和指针操作时,要用它来控制循环次数

edx

"数据寄存器’,在进行乘、除法运算时,可作为默认的操作数参数参与运算

esp

栈指针寄存器,存放函数栈顶地址

ebp

帧指针寄存器,存放函数栈底地址

esp和ebp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的 在本节中,主要了解这俩寄存器

三、汇编指令

Directives

Function

push x

将x压入栈中

pop x

将x弹出栈中

mov a, b

将b赋值给a,即b指向a

sub a

num a的值减去num,即a向低地址移动

lea(load effective adress)

加载有效地址(在示例中理解)

四、函数栈帧的创建

所有函数的调用都会在内存里面的栈区创建函数栈帧,包括main函数。

以下面一个详细的代码,描述函数栈帧的创建 本次代码是在 vs 2013 里面实现的,版本越低,可以更好展示

代码语言:javascript
复制
#include <stdio.h>
int Add(int x, int y)
{
	int z = x + y;
	return z;
}

int main()
{
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	return 0;
}

按F10,进行调试

在这里插入图片描述
在这里插入图片描述

4.1 main函数栈帧的创建

C语言所对应的汇编代码

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
int main()
{
  push        ebp           //将ebp压入栈中
  mov         ebp,esp       //将esp赋值给ebp,即将esp移动到ebp的位置
  sub         esp,0E4h      //将esp向低地址移动0E4h个字节的位置
  push        ebx           //(我们不要管)
  push        esi           //(我们不要管)
  push        edi           //(我们不要管)
  lea         edi,[ebp-24h]  //将[ebp-24h]存入edi中
  mov         ecx,9          //将9存入ecx中
  mov         eax,0CCCCCCCCh  //将0CCCCCCCCh存入eax中
  rep stos    dword ptr es:[edi]  //将edi的值对应的地址处开始,将高于该地址共ecx个单位的值置为0CCCCCCCCh
  
	int a = 10;
  mov         dword ptr [ebp-8],0Ah  
	int b = 20;
  mov         dword ptr [ebp-14h],14h  
	int c = Add(a, b);
  mov         eax,dword ptr [ebp-14h]  
  push        eax  
  mov         ecx,dword ptr [ebp-8]  
  push        ecx  
  call        011C10B4  
  add         esp,8  
  mov         dword ptr [ebp-20h],eax  
	return 0;
  xor         eax,eax  
}
  pop         edi  
  pop         esi  
  pop         ebx  
  add         esp,0E4h  
  cmp         ebp,esp  
  call        011C1235  
  mov         esp,ebp  
  pop         ebp  
  ret  

看着有点麻烦,不过对着汇编语言,可以仔细研究一番。

首先看main函数

在这里插入图片描述
在这里插入图片描述

栈使用空间是由高地址到低地址 正在调用哪个函数,esp和ebp就维护哪个函数,在这里,我们调用的是main函数,那么就维护main函数。

通过 __tmainCRTStartup 函数调用main函数,所以要创建好__tmainCRTStartup 的栈帧

push ebp
在这里插入图片描述
在这里插入图片描述

push ebp就是把__mainCRTStartup 函数栈底的地址压栈,ebp的值压入后,esp指针会上移一位

mov ebp,esp
在这里插入图片描述
在这里插入图片描述
sub esp,0E4h
在这里插入图片描述
在这里插入图片描述
push ebx / esi /edi
在这里插入图片描述
在这里插入图片描述
lea edi,[ebp-24h] 、mov ecx,9、mov eax,0CCCCCCCCh、rep stos dword ptr es:[edi]
在这里插入图片描述
在这里插入图片描述
main函数中变量的创建
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2 在main函数中调用Add函数

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
	int a = 10;
  mov         dword ptr [ebp-8],0Ah  
	int b = 20;
  mov         dword ptr [ebp-14h],14h  
	int c = Add(a, b);
  mov         eax,dword ptr [ebp-14h]  //把ebp-14h这个地址里面存放的值(也就是20)赋值给eax
  push        eax    //push(压栈)eax
  mov         ecx,dword ptr [ebp-8]  //把ebp-8这个地址里面存的值(也就是10)赋值给ecx
  push        ecx      //push(压栈)ecx
  
  
  call        011C10B4  
  add         esp,8  
  mov         dword ptr [ebp-20h],eax  
传参

经过这两次压栈后,esp指针指向的地址减小8个字节(一次减小4个字节,也就是1个整型)

在这里插入图片描述
在这里插入图片描述
进入Add函数

call 011C10B4 执行这条语句,在执行这条语句时,我们需要按键盘上的F11(笔记本电脑可能需要按Fn+F11)。

在按完F11后,我们就进入了Add函数内部,而且还会发现,esp指针还向上移动了4个字节

在这里插入图片描述
在这里插入图片描述

003F1450 是call指令的下一条指令的地址。

为什么要将call指令的下一条指令的地址存起来呢?? 是因为在Add函数调用结束的时候,需要返回继续执行call指令的下一条指令,所以在执行call指令的时候要将call下一条指令的地址存起来,在Add函数调用结束的时候,就能根据存的地址找到call指令的下一条指令,从而让程序可以继续执行。

紧接着继续按F11,就真正来到了Add函数里面

剩下的过程其实和在调用main函数的动画演示是一样的,不再做过多演示。

Add函数中变量Z的创建

此过程和main函数中变量a,b,c创建的过程是一样的

在这里插入图片描述
在这里插入图片描述
z=x+y
代码语言:javascript
复制
int z = x + y;
  mov         eax,dword ptr [ebp+8]  //把ebp+8这个地址里面存储的值放到eax里
  add         eax,dword ptr [ebp+0Ch]  //把ebp+0Ch这个地址里面存储的值加到eax里面去
  mov         dword ptr [ebp-8],eax  //把eax里面的值存到ebp-8这个地址里面(变量z的地址)
 return z;
  mov         eax,dword ptr [ebp-8]

五、函数栈帧的销毁

代码语言:javascript
复制
  pop         edi  
  pop         esi  
  pop         ebx  
  add         esp,0CCh  

会发现pop指令,代表出栈 将edi,esi和ebx弹出栈

add esp,8这条指令,该指令的执行结果是让esp指针指向的地址加8

在这里插入图片描述
在这里插入图片描述

mov dword ptr [ebp-20h],eax指令。执行结果是:把eax里面存的值(30)赋值给ebp-20h所指向的这块空间,其实就是变量c的存储空间

执行这条指令之前ebp-20h所指向的这块空间存的值是0,在执行完这条指令之后,ebp-20h所指向的这块空间里面存的就是1e(十进制下的30)。并且可以看出ebp-20h和&c的值相同,说明他们指向同一块空间——变量c的存储空间.

总结

本节涉及一些数据结构的内容,但是为了解决心中的疑惑,我们可以了解一下。 如有误,欢迎指正!!

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 观图有感
  • 一、概述
  • 二、寄存器
  • 三、汇编指令
  • 四、函数栈帧的创建
    • 4.1 main函数栈帧的创建
      • push ebp
      • mov ebp,esp
      • sub esp,0E4h
      • push ebx / esi /edi
      • lea edi,[ebp-24h] 、mov ecx,9、mov eax,0CCCCCCCCh、rep stos dword ptr es:[edi]
      • main函数中变量的创建
    • 4.2 在main函数中调用Add函数
      • 传参
      • 进入Add函数
      • Add函数中变量Z的创建
      • z=x+y
  • 五、函数栈帧的销毁
  • 总结
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档