}
**目录**
[1.基础理解](#1.%E5%9F%BA%E7%A1%80%E7%90%86%E8%A7%A3%C2%A0 "1.基础理解 ")
[1.1 寄存器](#1.1%20%E5%AF%84%E5%AD%98%E5%99%A8 "1.1 寄存器")
[1.2 函数](#1.2%20%E5%87%BD%E6%95%B0 "1.2 函数")
[1.3 栈帧](#1.3%20%E6%A0%88%E5%B8%A7%C2%A0 "1.3 栈帧 ")
[2.main函数栈帧的创建](#%C2%A02.main%E5%87%BD%E6%95%B0%E6%A0%88%E5%B8%A7%E7%9A%84%E5%88%9B%E5%BB%BA "2.main函数栈帧的创建")
[3.main函数栈帧的初始化](#3.main%E5%87%BD%E6%95%B0%E6%A0%88%E5%B8%A7%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96 "3.main函数栈帧的初始化")
[4.函数的调用](#4.%E5%87%BD%E6%95%B0%E7%9A%84%E8%B0%83%E7%94%A8 "4.函数的调用")
[5.函数的销毁](#5.%E5%87%BD%E6%95%B0%E7%9A%84%E9%94%80%E6%AF%81 "5.函数的销毁")
[6.问题解答](#6.%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94 "6.问题解答")
[3.函数是怎么传参的?传参的顺序是怎样的?形参和实参是什么关系?](#3.%E5%87%BD%E6%95%B0%E6%98%AF%E6%80%8E%E4%B9%88%E4%BC%A0%E5%8F%82%E7%9A%84%3F%E4%BC%A0%E5%8F%82%E7%9A%84%E9%A1%BA%E5%BA%8F%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84%3F%E5%BD%A2%E5%8F%82%E5%92%8C%E5%AE%9E%E5%8F%82%E6%98%AF%E4%BB%80%E4%B9%88%E5%85%B3%E7%B3%BB%3F "3.函数是怎么传参的?传参的顺序是怎样的?形参和实参是什么关系?")
[4.函数调用是怎么做的?](#4.%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E6%98%AF%E6%80%8E%E4%B9%88%E5%81%9A%E7%9A%84%3F "4.函数调用是怎么做的?")
[5.函数调用是结束后怎么返回的?](#5.%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E6%98%AF%E7%BB%93%E6%9D%9F%E5%90%8E%E6%80%8E%E4%B9%88%E8%BF%94%E5%9B%9E%E7%9A%84%3F "5.函数调用是结束后怎么返回的?")
---
## 1.基础理解
#### 1.1 寄存器
电脑中主要的本地存储方式有寄存器、内存、硬盘。硬盘最大,读取速度最慢。而寄存器的空间很小,但是速度是最快的,并且寄存器是集成在cpu上面的。它们可用来暂存指令、数据和地址。
>
> 通俗一点解释就是:
>
>
> 寄存器就是你的口袋。身上只有那么几个,只装最常用或者马上要用的东西。
>
>
> 内存就是你的背包。有时候拿点什么放到口袋里,有时候从口袋里拿出点东西放在背包里。
>
>
> 硬盘就是你家里的抽屉。可以放很多东西,但存取不方便。
>
>
>
本文我们研究的寄存器主要是:**ebp esp**
#### 1.2 函数
C语言是由函数为单元模块构成的,每一次我们用C语言编写程序的时候,都需要写一个主函数main()。我们了解函数栈帧的创建和销毁,其实就在研究一个代码是如何实现的。
我们需要用到的是 **汇编代码 。**
#### 1.3 栈帧
C语言中,每个栈帧对应着一个**未运行完的函数**。栈帧中保存了该函数的返回地址和局部变量。栈帧也叫**过程活动记录**,是编译器用来实现过程/函数调用的一种数据结构。首先应该明白,栈帧是存放在内存中的**栈区**的。栈帧是**栈区**分配给进程的**内存区**。

## 2.main函数栈帧的创建

当我们创建了一个main函数,ebp和esp寄存器里面存的就是这个函数的首位地址,用来维护这一块空间。
我们在进入这个程序的时候,首先需要**调用main函数**,你没听错,就是调用它。那么是谁调用的main函数呢?它是被一个简称叫做CRT的函数所调用的。
以VS2019为例,我们转到反汇编

可以看到这个seh函数调用了SRT函数(这里说法可能有误有错误请及时指出)
然后SRT函数再调用了main函数(这里说法可能有误有错误请及时指出)
用图表示出来就是esp和ebp在维护函数CRT的空间。我们接下来再看了解主函数是如何创建函数栈帧的。


**这里的push代表的是压栈**,就是将ebp放到栈顶。并且esp的指针也要向低地址移动。此时变成了:
接着我们看下一条指令:

mov是move的缩写,意思是将esp赋值给ebp,实质是将esp里面的地址放到ebp里面。(也就是两个指针指向了同一位置)
这里提一句:此时ebp和esp没有在维护CRT函数了,但他的函数栈帧没有销毁,因为栈空间的使用只能先销毁低地址,再销毁高地址。即图中的从上往下销毁。
我们接下来看第三条指令:

sub是减法的缩写,意思是将esp这个地址减去E4这个16进制数字。E4转化为十进制就是228,那么就是说将esp从高地址向低地址移动228以后存放起来。变成了如下图:

接下来看第4,5,6条指令:

这就是把ebx、esi、edi放到栈顶。esp也会对应着变化。至此一个main函数的栈帧就创建成功了

## 3.main函数栈帧的初始化
我们往后面看四个指令,lea指的是load efficitve address,即加载有效地址,把后面的地址给edi。
mov就是move的意思,看到第三条和第四条指令,就是把ebp到edi之间的空间全部初始化成为
cc cc cc cc,这些 cc cc cc cc是随机值,被初始化的空间大小为24(16进制)。
dword即double word,就是4个字节。


可以很明显的看到,ebp到edi这一部分被初始化成为了cc cc cc cc(随机值)。

main函数栈帧开辟完了,接下来就是执行代码了。
## 4.函数的调用

接着往下看,这是一个move指令,意思是把这个地址存放到ecx中去,就是将返回的地址存放起来,等会再回访这个地址执行下一条指令。
 接下来就是a和b的初始化。把0A(16进制数)存放到ebp-8的地址中间去,把14(16进制数)存放到epb-14的地址中去。这样子a和b的值就存放到内存中去了。

这里a和b存放的位置取决于编译器,相隔多远也取决于编译器。
综上:局部变量a,b,c的创建就是先创建一个函数栈帧,然后在里面找到
一块空间,再把a,b,c放进去。

紧接着就要进行传参数的操作了。eax、ecx在此之前是没有出现过的寄存器,存放的是
ebp-14h的值,也就是b的值20,接着push一下。ecx也是同理,就变成了这样子(也可以看到函数传参的时候是先传递后面的参数)
至此,我们把调试的f10按钮改成按f11按钮,进入ADD函数的反汇编代码中,我们仍然一步一步的进行解析。我们看到了一个call:

这个call指令是干什么的呢?call指令call了一个地址,这个地址是什么呢?
其实这个地址是执行完ADD函数以后返回来的一个地址,到时候ADD函数销毁以后直接找到这个地址就能继续完成代码。
再次按下f11,反汇编代码就进入了ADD函数的内部,我们一步一步来看:

这个ebp就是main函数的ebp,此时变成了这样子:

经过之前的一系列操作以后,又出现了跟main函数相同的内容:把esp的值赋给ebp,esp向上移动0CCh,再push ebx,esi,edi

这样子ADD函数就开辟好了空间,之后的操作也是跟main函数里面是一样的


接下来进入加法的部分

这里很关键,ebp+8和ebp+0Ch,这两个地方并不是新开创的空间,而是之前a和b传递的值的拷贝,也就是
也就是说传递的参数根本没有进入ADD函数里面去,而只是保存在寄存器里。
把两eax相加以后,再把eax的值放到ebp-8里面(也就是z)。这样子就完成了一次加法。
也可以证明,函数在调用的时候是自右向左传参的(ADD(a,b)先传b)。
同时再次证明了,**形参是实参的一份临时拷贝!**
## 5.函数的销毁
我们继续看反编译的代码:

这就是把30这个值放到eax这个全局的寄存器里面去,,然后接着的是三个pop(pop就是在栈顶弹出这个元素,可以理解为销毁):

但是还有很大一块ADD开辟的空间还存在,怎么办呢?

很简单,只要把ebp赋给esp就行了。这时候ebp和esp指向同一个地址,上面的ADD开辟的空间就被销毁了。计算出来的值已经存到eax中了。

紧接着:
pop了ebp,这个ebp是main函数的ebp。main函数的栈顶是很容易找到的,栈底并不好找。于是可以把ebp存在原来main函数里面,先push再pop后ebp回到得地方还是main函数栈底的位置。这样子ebp一下子就回到了栈底的位置。

之后出现了ret指令:

为什么要在栈顶存上一个call指令的下一条指令地址?其实ret指令返回的时候就是返回的call指令的下一条指令地址,那么就直接返回到了这个地址里面。
因为执行完call后,会直接进入到ADD函数中去,等到ADD函数销毁了,程序进行到这一步时,就会自然而然访问地址里面。
再往后就是eax的值存到了c中去
这样子,c的值就被打印出来了。
最后还要把形参销毁,这里销毁的方式比较简单,直接让esp加上8个字节,那么两个形参就不在esp和ebp的范围之内了。

## 6.问题解答
**1.局部变量是怎么创建的?**