目录
前言
🍤在我们前期的学习编程的过程中,我们会遇到许多诸如:“局部变量是如何创建的?”、“形参和实参有什么关系?”、“为什么局部变量是随机值?”、“函数调用时如何调用的?”等等的一些问题,今天我就来给你们讲解一下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开辟了一块空间,之后通过指针的偏移量找到我们的形参