💘作者:你我皆为凡人
💘博客主页:你我皆凡人的博客
💘名言警句:时间不会为任何人停留,而事物与人,无时不刻也在变化着。每一个人,也都在不停向前!
💘觉得博主文章写的不错的话,希望大家三连(✌关注,✌点赞,✌评论),多多支持一下!!
文章目录
目录
前言
这期给大家带来一期函数栈帧的创建和销毁,可以说只要把这个给搞明白,那么对于函数这一块儿的理解真的是如鱼得水,下面就来观看凡人带来的文章吧
提示:以下是本篇文章正文内容,下面案例可供参考
💫 什么是函数栈帧?
我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。那函数是如何调用的?函数的返回值又是如何待会的?函数参数是如何传递的?这些问题都和函数栈帧有关系。函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间这些空间是用来存放:函数参数和函数返回值临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。
💫 理解函数栈帧能解决什么问题?
理解函数栈帧有什么用呢?只要理解了函数栈帧的创建和销毁,以下问题就能够很好的理解了:局部变量是如何创建的?为什么局部变量不初始化内容是随机的?函数调用时参数时如何传递的?传参的顺序是怎样的?函数的形参和实参分别是怎样实例化的?函数的返回值是如何带回的?
💫 什么是栈?
栈很重要,每一个程序都需要用到栈,没有栈的存在,就没有局部变量,函数的存在,没有我们如今看到的计算机语言的存在栈就相当于一种容器,用户可以将数据存入里面(入栈),也可以将存入栈中的数据弹出(出栈),但是需要遵守一条规则,先入后出,就是先入栈的数据要后出栈,像很多书堆在一起,先放的书在最下面,最后才能取出来入栈使得栈增大,出栈使得栈减小,在经典的操作系统中,栈总是由下增长的(就是从高到低的来)
💫 认识相关寄存器和汇编指令
相关寄存器eax:通用寄存器,保留临时数据,常用于返回值ebx:通用寄存器,保留临时数据ebp:栈底寄存器esp:栈顶寄存器eip:指令寄存器,保存当前指令的下一条指令的地址相关汇编命令mov:数据转移指令push:数据入栈,同时esp栈顶寄存器也要发生改变pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变sub:减法命令add:加法命令call:函数调用,1. 压入返回地址 2. 转入目标函数jump:通过修改eip,转入目标函数,进行调用ret:恢复返回地址,压入eip,类似pop eip命令
💫 正式解析函数栈帧的创建和销毁
预备知识:在寄存器中有 eax,ebx,ecx,edx, 而想要了解函数栈帧,需要知道ebp,esp这两个寄存器中存放的是地址,而这两个地址是用来维护函数栈帧的,每一个函数调用,都要在栈区创建一个空间
如:add函数的函数栈帧的实现:
main函数也是被其他函数调用的:__tmainCRTStartup ,而这个函数又是被mainCRTStartup调用的
把main函数来转汇编语言来更好的分析:
int main()
{
00AD1410 push ebp
00AD1411 mov ebp,esp
00AD1413 sub esp,0E4h
00AD1419 push ebx
00AD141A push esi
00AD141B push edi
00AD141C lea edi,[ebp+FFFFFF1Ch]
00AD1422 mov ecx,39h
00AD1427 mov eax,0CCCCCCCCh
00AD142C rep stos dword ptr es:[edi]
int a = 10;
00AD142E mov dword ptr [ebp-8],0Ah
int b = 20;
00AD1435 mov dword ptr [ebp-14h],14h
int c = 0;
00AD143C mov dword ptr [ebp-20h],0
c = add(a, b);
00AD1443 mov eax,dword ptr [ebp-14h]
00AD1446 push eax
00AD1447 mov ecx,dword ptr [ebp-8]
00AD144A push ecx
00AD144B call 00AD10E6
00AD1450 add esp,8
00AD1453 mov dword ptr [ebp-20h],eax
printf("%d\n", c);
00AD1456 mov esi,esp
printf("%d\n", c);
00AD1458 mov eax,dword ptr [ebp-20h]
00AD145B push eax
00AD145C push 0AD5858h
00AD1461 call dword ptr ds:[00AD9114h]
00AD1467 add esp,8
00AD146A cmp esi,esp
00AD146C call 00AD113B
return 0;
00AD1471 xor eax,eax
}
💫 关于add函数反汇编的解析
1: 先从main函数开始开辟空间
int main()
{
//先是准备工作:为main函数创建空间
00AD1410 push ebp
//把ebp中的值进行压栈,存放进去放入刚开的invoke_main中的ebp
00AD1411 mov ebp,esp
//开始移动,把esp中的值放入ebp中,为main函数开辟了ebp
00AD1413 sub esp,0E4h
//给esp减去一个0E4h
00AD1419 push ebx
//压栈入一个ebx
00AD141A push esi
//压栈入一个esi
00AD141B push edi
//压栈入一个edi
00AD141C lea edi,[ebp+FFFFFF1Ch]
//lea为加载 ,把[ebp+FFFFFF1Ch]加入到edi中
00AD1422 mov ecx,39h
//把39h放到ecx中
00AD1427 mov eax,0CCCCCCCCh
//把 0CCCCCCCCh放入到eax中
00AD142C rep stos dword ptr es:[edi]
//从edi开始向下39h次的四个字节的数据,空间都改成eax的内容,改成0CCCCCCCCh
//开始a,b,c的创建,代表着正式开始
int a = 10;
00AD142E mov dword ptr [ebp-8],0Ah
//把0Ah的值放入ebp-8的地方 就是把10放进去
int b = 20;
00AD1435 mov dword ptr [ebp-14h],14h
//把14h的值放入到ebp-14h的地方,就是把20放进去
int c = 0;
00AD143C mov dword ptr [ebp-20h],0
//把0的值放入到ebp-20h的地方,就是把0放进去
//开始add函数的创建
c = add(a, b);
00AD1443 mov eax,dword ptr [ebp-14h]
//把ebp-14h放入到eax中,就是把b的20放入eax中
00AD1446 push eax
//开始压栈
00AD1447 mov ecx,dword ptr [ebp-8]
//把ebp-8放入到ecx中,就是把a的10放入到ecx中
00AD144A push ecx
//开始压栈
00AD144B call 00AD10E6
//开始调用
2:开始为add函数开辟空间
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int add(int x, int y)
{
00AD13C0 push ebp
//压栈
00AD13C1 mov ebp,esp
//把esp给了ebp
00AD13C3 sub esp,0CCh
//给esp减上0CCh
00AD13C9 push ebx
00AD13CA push esi
00AD13CB push edi
//压栈 放入三个元素
00AD13CC lea edi,[ebp+FFFFFF34h]
00AD13D2 mov ecx,33h
00AD13D7 mov eax,0CCCCCCCCh
00AD13DC rep stos dword ptr es:[edi]
//把[ebp+FFFFFF34h]加载到edi里,往下33h次的空间放入0CCCCCCCh
int z = 0;
00AD13DE mov dword ptr [ebp-8],0
//把0放到ebp-8的这里
z = x + y;
00AD13E5 mov eax,dword ptr [ebp+8]
//把ebp+8放入到eax中,就是10放入到eax
00AD13E8 add eax,dword ptr [ebp+0Ch]
// 把ebp+0ch放入到eax中,把10加上20放到eax中
00AD13EB mov dword ptr [ebp-8],eax
//把eax中的30放入ebp-8的地方
return z;
00AD13EE mov eax,dword ptr [ebp-8]
//把ebp-8的值30放入eax寄存器当中
}
00AD13F1 pop edi
00AD13F2 pop esi
00AD13F3 pop ebx
//一个一个的释放弹出
00AD13F4 mov esp,ebp
//把ebp赋值给esp
00AD13F6 pop ebp
//最后把ebp给弹出
00AD13F7 ret
3:最后回到add函数中,将寄存器里的值给传回来的过程
00AD1450 add esp,8
//esp+8指向x,y,把xy的值10,20给释放
00AD1453 mov dword ptr [ebp-20h],eax
// 把eax的值放入到ebp-20h,也就是c当中
printf("%d\n", c);
00AD1456 mov esi,esp
//把esp赋值给esi中
printf("%d\n", c);
00AD1458 mov eax,dword ptr [ebp-20h]
//把ebp-20h,c的值30移到eax中
00AD145B push eax
00AD145C push 0AD5858h
//弹出 释放
00AD1461 call dword ptr ds:[00AD9114h]
00AD1467 add esp,8
00AD146A cmp esi,esp
00AD146C call 00AD113B
return 0;
00AD1471 xor eax,eax
}
💫 函数栈帧的创建过程(画图详解)(可能看起来有一点点乱)
💫 函数栈帧的销毁(画图详解)(可能看起来有一点点乱)
总结
本篇文章详细介绍了 函数栈帧的创建与销毁,相信看到这里,大家对于开头的那几个问题都能应答上来了,这个内容可以说是修炼内功,只要把这一块儿理解清楚了,对后面的理解有很大的帮助,凡人也是稍微理解,用图片和代码的方式给一些初学者一些帮助,如果有哪里写的不清楚,或者不对的地方请大佬指正,一定听取意见,如果觉得凡人的文章写的还不错,希望大家多多支持,感谢大家的观看!!!!