函数栈帧的创建与销毁
本文主要讲解了函数调用过程中其栈帧的创建与销毁,内容干货较多,希望大家认真品味。
使用C语言进行函数调用时,是否会有很多疑问:
 1.局部变量是如何创建的?
 2.局部变量在未初始化的情况下,其值为什么会是随机值?
 3.函数是如何进行传参的?
 4.函数在传参的过程中顺序是怎样的?
 5.形参和实参是什么关系?
 6.函数调用是如何实现的?
 7.函数在调用结束后如何返回的?
这些问题应该都困扰大家许久,本文应该会给大家提供些许思路!
在进行讲解之前,大家应该都会了解C语言的一个关键字register——寄存器: 【关键字】——register在C语言中的使用,寄存器中有eax,ebx,ecx,edx,ebp,esp等。
在函数栈帧中会存在ebp,esp俩个寄存器,这俩个寄存器中存放的是地址,这俩个地址是用来维护函数栈帧的。
每一次的函数调用都会在栈区开创一个空间(包括main函数)。
#include<stdio.h>
int add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main(void)
{
	int a = 10;
	int b = 20;
	int ret = 0;
	ret = add(a, b);
	return 0;
}
 
在这里我们使用vs2022来观察函数栈帧的创建与销毁。
同时,本次调试的环境是X86环境

- 将代码转为反汇编
 

- 打开监视、调用堆栈、内存方便观察
 

- 转为反汇编
 

在函数调用开始前,我们应该了解到main函数也是被其他函数调用的,这点在vs2022中没有体现

main函数是被__mainCRTStartup函数调用的,__mainCRTStartup函数是被mainCRTStartup函数调用的。
esp与ebp俩个寄存器是用来维护函数栈帧的俩个寄存器。

 首先执行第一条汇编代码:push ebp
push——压栈,即从栈顶存放一个数据
 pop——出栈,即从栈顶拿出一个数据

此时,将epb的地址存放在__mainCRTStartup函数顶部,移动寄存器esp

执行下一条汇编代码:move ebp,esp
move——即将后面的值赋给前面

 此时,将esp的地址赋予ebp,二者指向同一地址

 下一个汇编代码:sub sep,0E4h
sub——即submit,将前面的数据减去后面数据
 0E4h——E4个字节,即228个字节

 此时,将寄存器esp向上移动228个字节

下面三条汇编代码:
 push ebx
 push esi
 push edi
 即将ebx,esi,edi三个寄存器压栈

此时,将ebx,esi,edi三个寄存器的地址放入顶部

下面一条汇编代码:lea edi,[ebp-24h]
 lea——load effective address即加载有效地址
 将[ebp-24h]的地址加载到edi寄存器上

此时,处于栈区的edi记录了ebp-24h的地址
 
 ebp-24h的地址为:0x0095FAEC

下面一条汇编代码:mov ecx,9
 move——将后面的值赋给前面,即将9保存在寄存器ecx中

 该寄存器的地址未保存在栈,这里阐述一下,将寄存器压栈,即保存寄存器的地址,然后改变寄存器内部数据是不影响寄存器在栈地址的保存的,如果了解指针的话,对这里的认知应该会更加清晰的。

 下一条汇编代码:move eax,0CCCCCCCCh
 将0CCCCCCCCh移动正在eax寄存器中

 此时,将0CCCCCCCCh移动到寄存器中

 下一条汇编代码:rep stos dword ptr es:[edi]
 rep——repeat重复操作
 stos——store string 存储字符串
 rep stos——重复存储字符串操作
 dword——double word双字,即四个字节
 ptr——pointer指针
 即用指针找到edi上存储的[ebp-24h],将[ebp-24h]下面的9个dword,即9个四个字节的数字初始化为0CCCCCCCCh.

 由于画图面积有限,画出初始化部分0CCCCCCCCh.

 可以在内存窗口看见初始化部分。

 下一条汇编代码:call 00CA1320
 call——跳转到子程序的地址
 此时,表明已经创建好一个函数,即main函数已经创建完成,通过call一个地址,进入此函数中

 下一条汇编代码:mov dword ptr [ebp-8],0Ah
 0Ah——转为十进制为10
 通过一个指针找到[ebp-8]的地址,将0Ah的值放入该地址

此时,在栈区存储局部变量0Ah

 通过地址可以查看到[ebp-8]这个地址的存储值

 下一条汇编代码:mov dword ptr [ebp-14h],14h
 14h——转为十进制为20
 通过一个指针找到[ebp-14h]的地址,将14h的值放入该地址

 此时,在栈区存储局部变量014h

 通过地址可以查看到[ebp-14h]这个地址的存储值

 下一条汇编代码:mov dword ptr [ebp-20h],0
 通过一个指针找到[ebp-20h]的地址,将0的值放入该地址

 此时,在栈区存储局部变量0

通过地址可以查看到[ebp-20h]这个地址的存储值

下一条汇编代码:mov eax,dword ptr [ebp-14h]
 将[ebp-14h]这个地址保存的【值】保存在eax寄存器中

此时,寄存器eax中保存[ebp-14h]中的值

 下一条汇编代码:push eax
 将寄存器eax的地址压栈

 此次行动可以称为传参。

 可以通过监视器观察到,寄存器中存的是20这个值,而非地址

下面俩条代码是同样的操作:
 mov ecx,dword ptr[ebp-8]
 push ecx
 将[ebp-8]这个地址里的值保存在ecx寄存器中,利用寄存器将值压栈

传参过程,即将[ebp-8]的保存的值传到寄存器ecx中

可以通过监视器观察到,寄存器中存的是10这个值,而非地址

 下一条汇编代码:call 00921023
 即跳转到子程序中,此时点击F11,进入add函数内部

接下来的操作,是创建函数过程,与创建main函数过程相同,本人使用图例演示。
下一条汇编代码:push ebp
 
下一条汇编代码:mov ebp,esp

 下一条汇编代码:sub esp,0CCh

下一条汇编代码: push ebx

下一条汇编代码: push esi

下一条汇编代码: push edi

下一条汇编代码: lea edi,[ebp-0Ch]

下一条汇编代码: mov ecx,3

下一条汇编代码: mov eax,0CCCCCCCCh

下一条汇编代码:rep stos dword ptr es:[edi]

下一条汇编代码:mov ecx,92C008h

下一条汇编代码: call 00921320


下一条汇编代码:mov dword ptr [ebp-8],0


下一条汇编代码:mov eax,dword ptr [ebp+8]

将[ebp+8]中存储的20放入寄存器中

下一条汇编代码:add eax,dword ptr [ebp+0Ch]

将俩个地址的值放在寄存器中相加

下一条汇编代码:dword ptr [ebp-8],eax

 将寄存器eax中的数值放在[ebp-8]

下一条汇编代码:eax,dword ptr [ebp-8]

将z中的数值保存在寄存器中

下一条汇编代码:pop edi
 pop——出栈,即从顶上拿出一个元素

 edi被释放

 下一条汇编代码:pop esi

 esi被释放

下一条汇编代码:pop ebx

 ebx被释放

下一条汇编代码:esp,0CCh

将维护函数的esp向栈底移动
 
 下一条汇编代码:cmp ebp,esp
cmp——compare比较,功能相当与减法指令,但不保存结果

 下一条汇编代码:call 00461244
 即跳转到下一个程序中。

下一条汇编代码:mov esp,ebp

 释放add函数,将esp与ebp寄存器返回

 下一条汇编代码:pop ebp

释放存放ebp的地址

下一条汇编代码:ret
ret——return,返回

 下一条汇编代码:add esp,8

将esp向下移动8个字节,释放存放局部变量的地方

 下一条汇编代码:mov dword ptr [ebp-20h],eax

 将寄存器eax中存放的30放在[ebp-20h]地址中

下一条汇编代码:xor eax,eax
 xor——XOR指令进行按位逻辑异或操作,将结果存放在目标操作数中1。它可以将一个寄存器的值和一个常量进行异或操作,结果会存储到原寄存器中2。
 大家还记得异或操作符吗?——相同为0,相异为1

那么俩个相同的值进行异或会?哈哈当然会变成0啊!

下面这些操作是释放main函数,main函数也是被调用的

- 今天的内容就到这里啦!希望大家可以提出建议!!!让我多多学习!!
 





![YOLOv8_seg的训练、验证、预测及导出[实例分割实践篇]](https://img-blog.csdnimg.cn/direct/d5645fe9e2a246d98f59f9e4e4fef5f3.png)













