评论

收藏

[C++] #yyds干货盘点#函数栈帧

编程语言 编程语言 发布于:2021-12-29 22:24 | 阅读数:582 | 评论:0

浅谈函数栈帧
很抱歉我用的是vs19,这个笔记本不可以装两个vs,性能不够,不是什么好电脑,但vs19只有调用main函数的那个“东西”看不到,其他的还是可以看到很多的,在开辟空间上优化很多。废话不多说直接上
错误的地方本文有的地方ebp写成edp 由于修改地方太多就不改了,基本是写到一半才发现哎

什么叫栈帧
可以直接说他就是一个空间,再准确点是存储空间。

该明白的一点知识
1.ebp(栈底指针),esp(栈顶指针)这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。
2.从高地址向低地址移动
3.压栈push :esp上移也就是朝低地址移动
​    出栈pop:也叫弹出,栈顶元素弹出,esp下移
4.每一个函数调用,都要在栈区创建一个空间

讲例
#include<stdio.h>
int Add(int a, int b)
{
  int sum = 0;
  sum = a + b;
  return sum;
}
int main()
{
  int a = 10;
  int b = 20;
  int c = 0;
  c = Add(a, b);
  printf("%d\n",c);
  return 0;
}
讲例就不说了大家都可以看的明白。

调试
我们首先进入调试,然后进入反汇编,只有底层才能讲的透彻。打开调用堆栈窗口。我们粗略调一遍会发现在调用堆栈里面我们看到了main函数,说明什么,有人在调用它,vs19我没看到,所以借vs13的来用一下


我们非常模糊的看到main函数被__tmainCRTStartup()调用,而这个函数还被mainCRTStartup调用


push ebp 就是说push压栈 把ebp压到esp顶部导致esp上移
mov  ebp,esp   是把esp的地址交给edp
这两步实现了esp,edp都指向了同一个地址


sub  esp,0E4h  sub是把esp减去0E4h导致esp上移也就是开辟了空间


3push   依次压栈edx,esi,edi


lea (load effective address)加载有效地址
word是两个字节,dword(double word)双字四字节
上面的意思就是从edi开始向下的9个 字节全都初始化成eax的内容CCCC...

(这里当时忘了监测,重新调试了,所以地址都变了)但基本现象没怎么变到了这里也就意味着main函数栈帧的开辟 也就准备完了

mov 把0Ah(也就是10)放到ebp-8的位置上,而ebp-8实际上就是为a开辟一个空间

至于为什么a,b,c为什么不是紧密排的,这是编译器的原因,是为了防止数据紧密容易出错

mov  把ebp-20给eax,上面可以看到那是b的空间,然后把eax压栈一下,把a给ecx再把ecx压栈一下,然后call我愿称他是奇计,我们可以非常清楚的看到a'上面的就是call指令的下一条指令的地址。他这一步是 在调用函数的同时把下一条指令的地址给压上去,这是回来的标记



这时才是真正的来到我们的Add函数上面和我们讲main函数栈帧一样

参数是从右向左压栈的,从上面我们也可以清楚的看到形参不是在Add函数内部创建的,而是回来找我们传参的空间,也直接的证明了形参是实参的临时拷贝这句话

请注意大风大浪过去了,千万不要在阴沟里面翻船
这里就是返回的变量明明销毁了,那如何把值给带回来的。把ebp-8里面的值也就是sum 放到eax里面,这里的eax可是寄存器啊,寄存器是不会因为程序退出就销毁的,相当于拿一个全局的寄存器把他保存起来,等我们到main函数里面再把它拿出来

pop弹出  把栈顶的元素取出放到edi里面去,依次pop三次,esp就加4加4往下走。当我们函数调用完了那这个空间就没必要存在了,所以把ebp的地址给esp

当esp指到这的时候pop一下把栈顶的元素弹出来,他里面放的是main函数的栈底指针,把结果弹到ebp里面去就可以瞬间到main栈底了
ret这条指令就是栈顶弹出call下一条指令地址然后跳过去,回来后就到了call下一条指令地方

这个add 就是把形参的空间还给操作系统

然后把eax的值给ebp-32空间就是c空间

结束
到了这里也就结束啦,只是非常浅显的讲解了一点,肯定还有好多地方没有讲到,寄存器这些就更底层了,也不是我们函数栈帧讲的主要内容,有机会以后再写寄存器的文章吧。


关注下面的标签,发现更多相似文章