《C语言初阶进阶完整教程》- 已完结 - 函数调用过程解析-函数栈帧的创建与销毁

 

⭐️本篇博客我要给大家分享一下函数栈帧的创建与销毁。希望对大家有所帮助。
⭐️ 博主码云gitee链接:码云主页


前言

🍤在我们前期的学习编程的过程中,我们会遇到许多诸如:“局部变量是如何创建的?”、“形参和实参有什么关系?”、“为什么局部变量是随机值?”、“函数调用时如何调用的?”等等的一些问题,今天我就来给你们讲解一下VS2019编译器下的函数栈帧的创建与销毁,当然不同的编译器底下的这个过程有略微的差异,大体上还是相同的。


🌏一、提前了解知识

        🍯1.部分汇编语言

  • mov:数据转移指令

  • push:数据入栈,同时esp栈顶寄存器往低位走

  • pop:数据弹出至指定位置,同时esp栈顶寄存器往高位走

  • sub:减法

  • add:加法

  • call:函数调用。1.压入返回地址;2.转入目标函数

  • mov a b:将b赋值给a,c语言表示就是a=b。

    sub a b:将a-b的结果赋值给a,c语言表述就是a=a-b。

    add a b :将a+b的结果赋值给a,c语言表述就是a=a+b。

         🍯2.函数栈帧术语

🍤栈顶上插入元素-压栈

🍤栈顶删除元素-出栈

        🍯3.ebp和esp

🍤 ebp(栈底指针), esp(栈顶指针)这2个寄存器中存放的是地址这2个地址是用来维护函数栈帧的。

#include <stdio.h>

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
	int a = 10;
	int b = 20;
	int c = 0;

	c = Add(a, b);
	printf("%d\n", c);
	return 0;
}

🌏二、函数栈帧的创建与销毁的具体过程

        🍯1.main函数的函数栈帧

🍤开辟栈区先在高地址开辟,然后在低地址开辟,之后i通过esp和ebp来维护开辟的空间

🍤 main函数在__mainCRTStartup内部被调用,__mainCRTStartup在mainCRTStartup函数内部被调用。

🍤 首先push esp 就是将esp地址减小,也就是esp向上移动了

🍤接下来就是mov ebp和esp,mov是把后一个值赋给前一个值的意思 ,两个寄存器地址会相同

 

🍤接下来就是sub就是减去,esp指针内容地址都减去0E4h,esp指针就向上移动,两个指针共同来维护为main函数新开辟的函数栈帧

🍤接下俩依次吧ebx,esi,edi三个元素push进去

 

🍤接下来是lea,lea是加载的意思,把后面的值加载到edi中 

 🍤将从edi地址开始向下到esp这些内容全部改成0xcccccccc

        🍯2.变量的创建

 🍤在ebp-8的位置放入0Ah(10),ebp-14h的位置放入14h(20),ebp-20h的位置函数

        🍯3.Add函数栈帧的创建与销毁

🍤进入函数

push    ebp                  //栈顶压入ebp
mov     ebp,esp              //将esp内容放入ebp(移动ebp)
sub     esp,0CCh             //esp+0CCh(为Add开辟空间)
push    ebx                  //在栈顶放入ebx
push    esi                  //在栈顶放入esi
push    edi                  //在栈顶放入edi
lea      edi,[ebp-0Ch]       //从ebp-0Ch的空间往下到ebp这块空间内
mov      ecx,3               //操作的次数为3次 
mov      eax,0CCCCCCCCh      //存初始化为0CCCCCCCCh
rep stos  dword ptr es:[edi] //esp往下0ch的空间初始化
mov      ecx,0FC003h         //
call     000F1307            //call指令是调用函数且记住下一条指令的地址。先进入函数,方便之后出函数可以找到call的下一条指令

 

🍤创建临时变量 

mov    eax,dword ptr [x]//将x的值放入eax
add    eax,dword ptr [y]//将y的值加道eax中,即x+y   
mov    dword ptr [z],eax//将eax的值放入变量z

pop     edi//出栈,删除为edi创建的栈区
pop     esi//pop指令会将esi的值放入esi(等于没变)  
pop     ebx//每pop一次,esp就往高位移动一次  
    
add     esp,0CCh//为esp地址+0CCh,即退出Add程序的栈区空间 
cmp     ebp,esp//将esp的值与ebp进行比较

 🍤每pop一次,esp就从高地址向低地址移动一位

call        __RTC_CheckEsp (0241244h)  
mov         esp,ebp//ebp的值赋给esp,此时esp和ebp相同

pop         ebp//弹出ebp  

🍤将ebp所指向的main函数的起始地址赋值给了ebp指针

🍤esp指针向高位移动一位

        🍯4.回到main函数

ret  

🍤前面提到call _Add (01A10B4h)这条指令非常重要,实际上,在执行ret指令时,esp指针就指向了栈顶存放的call指令的下一条指令的地址,同时,这个地址也被pop掉了 

mov      dword ptr [c],eax//将eax中的值放入变量c  

 🍤此时eax中存放的就是Add函数的返回值

🍤自定义函数的返回值是通过寄存器这一中间“变量”,返回主函数中的

         🍯5.结束程序

	return 0;
xor    eax,eax        //xor指令是异或--在这里的作用不清楚
}
pop    edi            //出栈--esp对应移动  
pop    esi            //  
pop    ebx            //  
add    esp,0E4h       //esp+0E4h(退出为main函数开辟的空间) 
cmp    ebp,esp        //比较ebp和esp  
call   __RTC_CheckEsp (0241244h)  
mov    esp,ebp        //将ebp的值复制给esp 
                      //此时esp和ebp的值依旧相同    
pop    ebp            //ebp出栈--esp和ebp分离
ret                   //main函数结束  



🌏总结

🍤1.局部变量怎么创建的?
        首先我给函数分配好栈帧空间,栈帧空间里面我们初始化一部分空间后,然后给局部变量分配空间 
🍤2.为什么局部变量不初始化的时候是随机值?
        随机值是我们放进去的
🍤3.函数是怎么传参的?
        当我要调用函数之前,我们就push开辟了一块空间,之后通过指针的偏移量找到我们的形参

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

penguin_bark

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

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

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

打赏作者

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

抵扣说明:

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

余额充值