当前位置: 首页 > news >正文

二手车网站软件建设wordpress com cn

二手车网站软件建设,wordpress com cn,wordpress 免密码破解,做的较好的拍卖网站函数栈帧的创建与销毁 导言一、计算机硬件1.冯•诺依曼机基本思想2.冯•诺依曼机的特点#xff1a;3.存储器3.1 分类3.2 内存的工作方式3.3 内存的组成 4.寄存器4.1 基本含义4.2 寄存器的功能4.3 工作原理4.4 分类4.4.1 通用寄存器组AX(AH、AL)#xff1a;累加器BX(BH、BL)3.存储器3.1 分类3.2 内存的工作方式3.3 内存的组成 4.寄存器4.1 基本含义4.2 寄存器的功能4.3 工作原理4.4 分类4.4.1 通用寄存器组AX(AH、AL)累加器BX(BH、BL)基址寄存器CX(CH、CL)计数寄存器DX(DH、DL)数据寄存器 4.4.2 指针和变址寄存器4.4.3 段寄存器4.4.4 指令指针寄存器4.4.5 标志寄存器 二、函数栈帧的创建1.调试窗口与调用堆栈2.main函数的调用3.main函数的函数栈帧的创建3.1反汇编3.2过程理解 三、局部变量的创建四、随机值的由来五、函数传参六、函数调用七、形参与实参八、函数栈帧的销毁九、函数的返回值结语 导言 本篇内容为函数的补充知识点——函数栈帧的创建和销毁。 在本篇内容中我们将会学习在函数篇章中未提到的一些知识点 局部变量是如何创建的为什么创建局部变量时如果不初始化局部变量的值会是随机值函数是怎么传参的传参的顺序又是什么形参和实参有什么关系函数是如何调用的调用结束后又是如何返回的 如果你对这些问题还是比较模糊的话可以好好阅读一下本篇文章。本篇文章的内容会帮助大家进一步学习和理解C语言的相关知识点。 今天介绍的环境是VS2019如果有朋友电脑上有安装VS2013、VS2010甚至是VC6.0的话能够更加容易学习和观察函数栈帧的创建与销毁这一过程。 在不同的编译器下这个过程会略有差异具体的细节是取决于编译器的实现。我们只需要通过这一篇内容学习到这个过程实现的逻辑就OK了。接下来我们就开始进入正题吧 在数组篇章我们有提到过一个概念——寄存器。当时我们只是简单提及了一下CPU在读取数据时的顺序是 寄存器— 高速缓存— 内存 寄存器—高速缓存—内存 寄存器—高速缓存—内存寄存器究竟是个什么东西为什么CPU在读取时优先读取它呢下面围绕这两个问题我们来探讨一下 一、计算机硬件 1.冯•诺依曼机基本思想 冯•诺依曼在研究 EDVAC 机时提出了“存储程序”的概念。1 “存储程序”的基本思想 将实现编制好的程序和原始数据送入主存后才能执行一旦程序被启动执行就无序操作人员干预计算机会自动逐条执行指令直至程序执行结束。 “存储程序”的思想奠定了现代计算机的基本结构以此概念为基础的各类计算机通称为冯•诺依曼机。 2.冯•诺依曼机的特点 冯•诺依曼机有以下几个特点 采用“存储程序”的工作方式计算机硬件系统由运算器、存储器、控制器、输入设备和输出设备5大部件组成指令和数据以同等低位存储在存储器中形式上没有区别但计算机应能区分它们指令和数据均用二进制代码表示。指令由操作码和地址码组成操作码指出操作的类型地址码指出操作数的地址。 3.存储器 3.1 分类 存储器分为主存储器内存和辅助存储器外存。 CPU能够直接访问的存储器是主存储器。 辅助存储器用于帮助主存储器记忆更多的信息辅助存储器中的信息必须调入主存后才能为CPU所访问。 3.2 内存的工作方式 主存储器的工作方式是按存储单元的地址进行存取这种存取方式称为按地址存取方式。 3.3 内存的组成 内存最基本的组成是由 MAR 、存储体和 MDR 组成。 注 寄存器是用来存放二进制数据的这里的MAR存放的是地址信息、MDR暂存的是从存储器中读或写的数据信息MAR和MDR虽然是存储器的一部分但是在现代计算机中确实存在于CPU中的另外前面提到的高速缓存Cache也存在于CPU中。 4.寄存器 寄存器的功能是存储二进制代码它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码故存放n位二进制代码的寄存器需用n个触发器来构成。 按照功能的不同可将寄存器分为基本寄存器和移位寄存器两大类。基本寄存器只能并行送入数据也只能并行输出。移位寄存器中的数据可以在移位脉冲作用下依次逐位右移或左移数据既可以并行输入、并行输出也可以串行输入、串行输出还可以并行输入、串行输出或串行输入、并行输出十分灵活用途也很广。 4.1 基本含义 寄存器2是CPU内部用来存放数据的一些小型存储区域用来暂时存放参与运算的数据和运算结果。 寄存器是中央处理器(CPU)内的组成部分。寄存器是有限存储容量的高速存储部件它们可用来暂存指令、数据和位址。 在计算机领域寄存器是CPU内部的元件包括通用寄存器、专用寄存器和控制寄存器。寄存器拥有非常高的读写速度所以在寄存器之间的数据传送非常快。 4.2 寄存器的功能 寄存器最起码具备以下4种功能 清除数码将寄存器里的原有数码清除。接收数码在接收脉冲作用下将外输入数码存入寄存器中。存储数码在没有新的写入脉冲来之前寄存器能保存原有数码不变。输出数码在输出脉冲作用下才通过电路输出数码。 仅具有以上功能的寄存器称为数码寄存器有的寄存器还具有移位功能称为移位寄存器。 4.3 工作原理 在计算机及其他计算系统中寄存器是一种非常重要的、必不可少的数字电路构件它通常由触发器D触发器组成主要作用是用来暂时存放数码或指令。一个触发器可以存放一位二进制代码若要存放N位二进制数码则需用N个触发器。 寄存器应具有接收数据、存放数据和输出数据的功能它由触发器和门电路组成。 只有得到“存入脉冲”又称“存入指令”、“写入指令”时寄存器才能接收数据在得到“读出”指令时寄存器才将数据输出。 寄存器存放数码的方式有并行和串行两种。 并行方式是数码从各对应位输入端同时输入到寄存器中串行方式是数码从一个输入端逐位输入到寄存器中。 寄存器读出数码的方式也有并行和串行两种。 在并行方式中被读出的数码同时出现在各位的输出端上在串行方式中被读出的数码在一个输出端逐位出现。 4.4 分类 4.4.1 通用寄存器组 通用寄存器组包括AX、BX、CX、DX4个16位寄存器用以存放16位数据或地址。 也可用作8位寄存器。用作8位寄存器时分别记为AH、AL、BH、BL、CH、CL、DH、DL。只能存放8位数据不能存放地址。 它们分别是AX、BX、CX、DX的高八位和低八位。若AX1234H则AH12HAL34H。 通用寄存器通用性强对任何指令它们具有相同的功能。为了缩短指令代码的长度在8086中某些通用寄存器用作专门用途。例如串指令中必须用CX寄存器作为计数寄存器存放串的长度这样在串操作指令中不必给定CX的寄存器号缩短了串操作指令代码的长度。下面一一介绍 AX(AH、AL)累加器 有些指令约定以AX(或AL)为源或目的寄存器。输入/输出指令必须通过AX或AL实现例如端口地址为43H的内容读入CPU的指令为INAL43H或INAX43H。目的操作数只能是AL/AX而不能是其他的寄存器。 BX(BH、BL)基址寄存器 BX可用作间接寻址的地址寄存器和基地址寄存器BH、BL可用作8位通用数据寄存器。 CX(CH、CL)计数寄存器 CX在循环和串操作中充当计数器指令执行后CX内容自动修改因此称为计数寄存器。 DX(DH、DL)数据寄存器 除用作通用寄存器外在I/O指令中可用作端口地址寄存器乘除指令中用作辅助累加器。 4.4.2 指针和变址寄存器 BP( Base Pointer Register)基址指针寄存器。 SP( Stack Pointer Register)堆栈指针寄存器。 SI( Source Index Register)源变址寄存器。 DI( Destination Index Register)目的变址寄存器。 这组寄存器存放的内容是某一段内地址偏移量用来形成操作数地址主要在堆栈操作和变址运算中使用。 BP和SP寄存器称为指针寄存器与SS联用为访问现行堆栈段提供方便。 通常BP寄存器在间接寻址中使用操作数在堆栈段中由SS段寄存器与BP组合形成操作数地址即BP中存放现行堆栈段中一个数据区的“基址”的偏移量所以称BP寄存器为基址指针。 SP寄存器在堆栈操作中使用PUSH和POP指令是从SP寄存器得到现行堆栈段的段内地址偏移量所以称SP寄存器为堆栈指针SP始终指向栈顶。 注今天我们研究的函数栈帧的创建与销毁就与BP和SP这两个寄存器密切相关。 寄存器SI和DI称为变址寄存器通常与DS一起使用为访问现行数据段提供段内地址偏移量。 在串指令中其中源操作数的偏移量存放在SⅠ中目的操作数的偏移量存放在DI中SI和DI的作用不能互换否则传送地址相反。在串指令中SI、DI均为隐含寻址此时SI和DS联用Dl和ES联用。 4.4.3 段寄存器 8086/8088CPU可直接寻址1MB的存储器空间直接寻址需要20位地址码而所有内部寄存器都是16位的只能直接寻址6KB因此采用分段技术来解决。将1MB的存储空间分成若干逻辑段每段最长64KB这些逻辑段在整个存储空间中可浮动。 8086/8088CPU内部设置了4个16位段寄存器它们分别是 代码段寄存器CS、数据段寄存器DS、堆栈段寄存器SS、附加段寄存器ES 由它们给出相应逻辑段的首地址称为“段基址”。段基址与段内偏移地址组合形成20位物理地址段内偏移地址可以存放在寄存器中也可以存放在存储器中。 例如代码段寄存器CS存放当前代码段基地址IP指令指针寄存器存放了下一条要执行指令的段内偏移地址其中CS2000HIP001AH。通过组合形成20位存储单元的寻址地址为2001AH。 代码段内存放可执行的指令代码数据段和附加段内存放操作的数据通常操作数在现行数据段中而在串指令中目的操作数指明必须在现行附加段中。 堆栈段开辟为程序执行中所要用的堆栈区采用先进后出的方式访问它。 各个段寄存器指明了一个规定的现行段各段寄存器不可互换使用。 程序较小时代码段、数据段、堆栈段可放在一个段内即包含在64KB之内而当程序或数据量较大时超过了64KB那么可以定义多个代码段或数据段、堆栈段、附加段。 现行段由段寄存器指明段地址使用中可以修改段寄存器内容指向其他段。有时为了明确起见可在指令前加上段超越的前缀以指定操作数所在段。 4.4.4 指令指针寄存器 IP8086/8088CPU中设置了一个16位指令指针寄存器IP用来存放将要执行的下一条指令在现行代码段中的偏移地址。 程序运行中它由BIU自动修改使IP始终指向下一条将要执行的指令的地址因此它是用来控制指令序列的执行流程的是一个重要的寄存器。 8086程序不能直接访问IP但可以通过某些指令修改IP的内容。 例如当遇到中断指令或调用子程序指令时8086自动调整IP的内容将IP中下一条将要执行的指令地址偏移量入栈保护待中断程序执行完毕或子程序返回时可将保护的内容从堆栈中弹出到IP使主程序继续运行。 在跳转指令时则将新的跳转目标地址送入IP改变它的内容实现了程序的转移。 4.4.5 标志寄存器 FR标志寄存器FR也称程序状态字寄存器。 FR是16位寄存器其中有9位有效位用来存放状态标志和控制标志。 状态标志共6位CF、PF、AF、ZF、SF和OF用于寄存程序运行的状态信息这些标志往往用作后续指令判断的依据。控制标志有3位IF、DF和TF用于控制CPU的操作是人为设置的。 在简单了解了寄存器的相关知识点后接下来我们就要开始介绍函数栈帧了。 二、函数栈帧的创建 函数栈帧的创建与维护是通过 bp 和 sp 这两个寄存器实现的在汇编语言中这两个寄存器被称为 ebp 和 esp 。 这两个寄存器存放的是地址所以ebp又被称为栈底指针esp又被称为栈顶指针。 接下来我们就通过下面这个代码来介绍一下 ebp 和 esp 它们是如何创建和维护函数栈帧的 int Add(int x, int y) {int z 0;z x y;return z; } int main() {int a 2;int b 3;int c 0;c Add(a, b);printf(%d\n, c);return 0; }在这个代码中我们把每一步都细致的拆分出来了下面我们就来通过这个代码来分析 1.调试窗口与调用堆栈 为了更加清楚直观的看到main函数的调用过程与函数栈帧的创建过程我们需要对这个代码进行调试。步骤如下 第一步打开调试窗口 F 10 — 调试— 窗口— 调用堆栈 F10—调试—窗口—调用堆栈 F10—调试—窗口—调用堆栈 按照上述步骤我们可以从堆栈窗口中看到三行内容第一行显示的是现在调试的代码行第二行显示的是外部代码第三行不用管它 第二步显示外部代码 单击鼠标右键— 显示外部代码 单击鼠标右键—显示外部代码 单击鼠标右键—显示外部代码 这里建议大家可以同时勾选外部代码下面的帧状态不勾选也没关系不影响我们接下来的操作。在勾选完显示外部代码后我们会看到如图所示内容 可以看到我这里框选的代码在帧状态栏提示的是非用户代码接下来我们可以将鼠标移动到这四行内容的任意一行上来查看它的源代码 第三步转到源代码 单击鼠标右键— 转到源代码 单击鼠标右键—转到源代码 单击鼠标右键—转到源代码 在做完上述步骤后我们就能看到下图所示的窗口了 这里有一点需要注意我们通过前三行内容打开的窗口和第四行内容打开的窗口是有区别的至于为什么会有区别咱们接着往下看 2.main函数的调用 我们为了更好的观察main函数的调用过程这里我们从第四句内容的源代码来进行分析 在这个窗口中我们可以看到对于mainCRTStartup这个函数来说它在函数体内调用了__scrt_common_main并将这个函数的值返回给自己。那这个__scrt_common_main函数的值又是什么呢我们接着观察 选择函数名— 单击鼠标右键— 转到定义 选择函数名—单击鼠标右键—转到定义 选择函数名—单击鼠标右键—转到定义 在完成了这一步操作后我们就会看到这个界面 在这个界面中我们可以看到此时的__scrt_common_main函数它的值是__scrt_common_main_seh函数的值我们接着重复刚才的操作来观察一下__scrt_common_main_seh函数看看它的值是什么 这里代码比较多我们不用关心其它内容是什么我们先看这两个返回值返回的都是这个局部变量的值这个局部变量的值是invoke_main这个函数的值接下来我们继续观察invoke_main这个函数 现在我就可以看到了对于incoke_main函数它的值是main函数的返回值那我们现在就清楚了原来一个main函数是经过这种层层调用来实现的有细心的朋友就会发现在调用堆栈窗口中已经给我们把这个调用关系给展示出来了 到这里我们就已经弄清楚了main函数的调用过程。在介绍函数递归时我们有介绍过在进行函数调用时函数就会在栈区申请一块空间对于main函数也同样如此。如图所示 现在我们知道了main函数是被经过层层调用的也就是说它在栈区中实际情况应该如下所示 现在我们已经了解了main函数的调用过程接下来我们需要继续通过调试窗口来观察main函数的栈帧是如何创建的 3.main函数的函数栈帧的创建 3.1反汇编 为了观察这个过程接下来我们需要通过反汇编窗口来进行介绍可以通过下述步骤进入反汇编窗口 单击鼠标右键— 转到反汇编 单击鼠标右键—转到反汇编 单击鼠标右键—转到反汇编 在完成上述操作后我们就可以进入到反汇编界面了 在这个界面这些反汇编语句我们应该如何理解呢别着急我们先复习一下前面提到的寄存器的内容 通用寄存器组 AX(AH、AL)累加器。有些指令约定以AX(或AL)为源或目的寄存器。BX(BH、BL)基址寄存器。BX可用作间接寻址的地址寄存器和基地址寄存器BH、BL可用作8位通用数据寄存器。CX(CH、CL)计数寄存器。CX在循环和串操作中充当计数器指令执行后CX内容自动修改因此称为计数寄存器。DX(DH、DL)数据寄存器。除用作通用寄存器外在I/O指令中可用作端口地址寄存器乘除指令中用作辅助累加器。 指针和变址寄存器 BP( Base Pointer Register)基址指针寄存器。 通常BP寄存器在间接寻址中使用操作数在堆栈段中由SS段寄存器与BP组合形成操作数地址即BP中存放现行堆栈段中一个数据区的“基址”的偏移量所以称BP寄存器为基址指针。 SP( Stack Pointer Register)堆栈指针寄存器。 SP寄存器在堆栈操作中使用PUSH和POP指令是从SP寄存器得到现行堆栈段的段内地址偏移量所以称SP寄存器为堆栈指针SP始终指向栈顶。 SI( Source Index Register)源变址寄存器。 DI( Destination Index Register)目的变址寄存器。 寄存器SI和DI称为变址寄存器通常与DS一起使用为访问现行数据段提供段内地址偏移量。在串指令中其中源操作数的偏移量存放在SⅠ中目的操作数的偏移量存放在DI中SI和DI的作用不能互换否则传送地址相反。在串指令中SI、DI均为隐含寻址此时SI和DS联用Dl和ES联用。 接下里我们来看一下这些操作指令的含义 push——压栈给栈顶放一个元素mov——移动将第二个对象的值赋值给第一个对象sub——减将第一个对象减少第二个对象的值lea——load effective address——加载有效地址——将第二个对象的地址赋值给第一个对象rep stos——进行重复的存储操作call——调用 有了这些知识来做支撑现在我们是不是就跟容易理解这些反汇编代码了。 3.2过程理解 现在我们通过监视窗口和内存窗口来同步理解这些汇编语句打开这些窗口的步骤是一样的 调试— 窗口— 监视 / 内存— 监视 1 / 内存 1 调试—窗口—监视/内存—监视1/内存1 调试—窗口—监视/内存—监视1/内存1 我们打开这两个窗口之后还需要进行一步操作——取消显示符号名 如上图所示。在执行完这些操作后我们就可以在监视窗口来观察我们需要观察的对象了。首先我们先观察 esp 和 ebp 从监视窗口我们可以看到此时的 esp 指向的地址是0x00fcfbbc ebp 指向的是0x00fcfbd8按照前面我们分析的此时的 esp 和 ebp 指向的应该是incoke_main函数的栈顶和栈底 接下来我们需要执行的是压栈操作在压栈完 esp 指向的地址会发生变化 从这里我们可以看到此时 esp 指向的地址从0x00fcfbbc变成了0x00fcfbb8它们之间的差值为4也就是说这里我在栈顶放置了一个大小为4个字节的元素 我们继续执行下一步操作。下一步进行的是mov将esp的值赋值给ebp也就是说此时ebp执行的地址会变成0x00fcfbb8那是不是这样呢我们接着往下运行 可以看到此时确实如我们理解的这样栈底指针的地址现在执行的是栈底指针的地址此时两个指针是重合的紧接着我们可以看到它的下一步执行的是sub操作也就是将栈顶指针 esp 减少0E4h这么大的距离我们也可以通过监视窗口来观察这个距离的大小 可以看到这个0E4h的值为228也就是栈顶指针指向的值要减少228个字节减少之后的地址就是栈顶指针新指向的地址 可以看到 esp 新指向的地址为0x00fcfad4。那我们就能得到一块由 esp 和 ebp 所指向的新空间 这块新指向的空间是什么呢别着急我们接着往下看。 在得到新空间后紧接着做了三次压栈操作压入的对象分别是 ebx 、esi 、edi 我们通过监视窗口来看一下它们是谁 可以看到它们三个的值分别是 ebx 0x011a9000 esi 0x00c41023 edi 0x00c41023。 也就是说现在会在栈顶将这三个元素按顺序进行压栈操作下面我们就来观察一下 可以看到这些值依次被压入栈顶压入的空间大小为4个字节也就是说此时 esp 和 ebp 所指向的空间又增加了12个字节 接下来我们继续来观察后面的步骤。 之后程序依次进行了lea——加载有效地址这里是将ebp-24h这个地址赋值给了 edi 。这地址是多少呢我们通过监视窗口来观察一下 从监视窗口中我们可以看到24h的值转换成十进制其实是36根据这里的空间为4个字节来看总共相隔9个地址从内存窗口中我们可以看到确实与 ebp 的地址相隔9个地址的距离我们来看一下执行之后会是什么效果 紧接着程序执行了两次mov和一次rep stos。 第一次mov——将9这个值给了 ecx ecx 此时存放的空间数量为9 第一次mov——将0CCCCCCCCh这个值给了 eax 随后执行了rep stos——将从edi开始的空间的值全部修改成0CCCCCCCCh总共修改ecx个空间 下面我们来接着往下运行看一下程序执行后是什么效果 可以看到此时这9个空间内的值都被修改成了0CCCCCCCCh 接着程序执行了mov——将0C4C006h这个值赋值给了 ecx 随后程序执行的是一次call——调用指令它此时调用的是谁呢我也不知道所以我们可以通过F11来进一步观察一下 此时我们可以看到此时的call指令调用的是0x00C4131B这个地址中的指令此时这个地址中的指令为jmp——跳转指令它会跳转到哪里呢我们继续观察 原来call指令调用的是函数__CheckForDebuggerJustMyCode(unsigned char *JMC_flag) 这个函数我们可以简单的理解为只是对我们自己的代码进调试。在前面我们介绍main函数的调用时我们有看到一个main函数是需要经过层层调用的这个函数的用途就是将调用main函数的这些源代码给屏蔽掉只对我们自己的代码进行调试所以这里我就不过多介绍了。现在我们回到我们的代码 现在代码运行到了int a 2;这一行也就是说前面的过程都是在对main函数的栈帧进行创建创建好的main函数的栈帧情况如下所示 现在main函数的栈帧也开辟好了接下来就是要开始创建局部变量a、b、c了这些局部变量又是如何创建的呢我们接着往下看 三、局部变量的创建 对于局部变量的创建就没有像开辟main函数的空间那么复杂了 int a 2; 00C418D5 mov dword ptr [ebp-8],2 int b 3; 00C418DC mov dword ptr [ebp-14h],3 int c 0; 00C418E3 mov dword ptr [ebp-20h],0 从代码中我们可以看到对于这三个局部变量在创建时分别执行了一次mov指令我们来看一下这个指令时是如何执行的 变量a dword ptr [ebp-8],2 //dword——四个字节 //dword ptr——内存单元为四个字节的长度 //ebp-8——地址名 //2——移动对象这里的意思就是将2这个值移动到ebp-8这个地址上 变量b dword ptr [ebp-14h],3 //dword——四个字节 //dword ptr——内存单元为四个字节的长度 //ebp-14h——地址名 //3——移动对象这里的意思就是将3这个值移动到ebp-14h这个地址上 变量c dword ptr [ebp-20h],0 //dword——四个字节 //dword ptr——内存单元为四个字节的长度 //ebp-20h——地址名 //0——移动对象这里的意思就是将0这个值移动到ebp-20h这个地址上 从这个操作来看局部变量的创建就是在main函数的函数栈帧中分配一个地址并在这个地址中进行赋值操作那具体是不是这样呢我们现在来观察一下此时的内存空间的情况 此时我们还未创建局部变量我们现在可以观察到的是对应的这些地址的值为cccccccc 紧接着我们开始运行代码完成三个变量的创建此时会发生什么情况呢 可以看到经过mov操作后这三块地址存储的信息就变成了2/3/0此时这三个空间就是给变量a、b、c各自分配的空间 四、随机值的由来 现在大家来思考一下我们此时是给变量赋予了初始值所以才有了这三个空间中的值被修改了如果我要是在定义变量是不进行初始化那此时变量的值又会是什么呢 我相信现在大家都已经猜到了没错就是cccccccc这个值是我们在对main函数开辟空间是通过下述指令完成的 00C418BC lea edi,[ebp-24h] //将ebp-24h的地址赋值给edi 00C418BF mov ecx,9 //将9赋值给ecx 00C418C4 mov eax,0CCCCCCCCh //将0CCCCCCCCh赋值给eax 00C418C9 rep stos dword ptr es:[edi] //从edi开始往下走ecx个地址并将这些地址赋值eax的值现在大家就知道这些随机值是怎么来的了吧。我们继续往下看 五、函数传参 此时我们的变量也创建好了接下来就是要调用Add函数了我们来看一下此时程序做了哪些操作 00C418EA mov eax,dword ptr [ebp-14h] 00C418ED push eax 00C418EE mov ecx,dword ptr [ebp-8] 00C418F1 push ecx 它先通过mov指令将[ebp-14h]的值赋值给了 eax 紧接着就进行了压栈操作 随后又通过mov指令将[ebp-8]的值赋值给了 ecx 紧接着又进行了压栈操作 那具体是不是这样呢我们来看一下运行结果 从这个运行结果中可以看到确实和我们分析的一样此时的函数栈帧图像如下所示 可以看到这个传参的过程其实是在main函数的栈帧中完成的传参的顺序是从变量b开始再到变量a。对应的代码c Add(a, b);也就是说函数在传参时是按从右到左的顺序进行传参的 六、函数调用 我们接着往后看 可以看到此时要执行的是call指令也就是函数调用指令这里我们通过F11来细致的观察 我们现在已经进入了Add函数的内部此时通过观察 esp 指向的地址我们可以发现这个地址此时存储的值是call指令的下一条指令add的地址。为什么会这样呢别着急我们继续往下看 在Add函数中我们可以看到此时执行的操作与main函数前面的操作一模一样通过前面分析main函数可知此时我们需要进行的操作时为Add函数开辟一块空间这里我就不再重复演示开辟的过程了我们直接来到创建临时变量z这一行 此时我们就完成了Add函数的函数栈帧的创建接下来我们就要进行局部变量z的创建与函数形参的使用了 七、形参与实参 这里我们来分析一下代码 int z 0; 00C417A5 mov dword ptr [ebp-8],0 //在Add函数栈帧中给变量z分配一块空间 //空间大小为4个字节 //空间内存放的内容为0z x y; 00C417AC mov eax,dword ptr [ebp8] //将[ebp8]的值赋值给eax 00C417AF add eax,dword ptr [ebp0Ch] //将[ebp0Ch]的值累加到eax 00C417B2 mov dword ptr [ebp-8],eax //将eax的值赋值给[ebp-8]此时的函数栈帧图像如下所示 通过它这几步的指令我们现在可以理解了原来前面将变量a和b的值赋值给 ecx 和 eax 的操作原来是在给形参x、y在main函数的函数栈帧中分配空间啊。也就是说此时的形参x、y只是实参的一份临时拷贝而已对形参的修改并不会影响实参。 我们在Add函数中使用它们的值的时候只是通过eax这个寄存器将它们运算的值临时存储起来然后将这个值赋值给在Add函数栈帧中创建的局部变量z。 接下来就是要回到main函数了 八、函数栈帧的销毁 在回到main函数之前程序执行了如下操作 00C417B5 pop edi 00C417B6 pop esi 00C417B7 pop ebx 00C417B8 add esp,0CCh 00C417BE cmp ebp,esp 00C417C0 call 00C41244 00C417C5 mov esp,ebp 00C417C7 pop ebp 00C417C8 ret 这里涉及到的操作含义如下 pop——出栈从栈顶删除一个元素add——累加将第二个对象的值累加到第一个对象中cmp——cmp是比较指令 cmp的功能相当于减法指令只是不保存结果。cmp指令执行后将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。call——调用函数指令mov——移动将第二个对象的值赋值给第一个对象ret——子程序的返回指令 我们应该怎么理解这些指令呢 对于pop指令我们可以理解为释放空间或者说是销毁空间这里我们可以看到连续三个pop指令它的命令对象分别是 edi 、esi 、ebx 我们来看一下执行完这三条指令会发生什么现象 现在我们还没有执行指令我们先将下列信息记录下来 栈顶地址0x00FCF9E0esi 地址0x00FCF9E4ebx 地址0x00FCF9E8edi 地址0x00FCFAB8ebp 地址0x00FCFAB8 接下来我们开始执行pop操作 可以看到此时的栈顶释放掉了一个空间edi的值也发生了变化。继续进行pop 在经过第二次pop后此时的栈顶再一次释放掉了一个空间我们继续执行pop 经过第三次pop后此时的栈顶再一次释放掉了一个空间也就是说pop指令时来释放空间的此时的函数栈帧情况如下 从图中我们可以看到现在的Add的函数栈帧只剩下最开始的204个字节大小的空间了我们来看一下这个空间又是如何释放的 接下来进行的是cmp和call操作我们来观察一下此时会出现什么情况 在这里call指令调用的是函数_RTC_CheckEsp(void)这个函数是用于验证 esp 的正确性。它被调用以确保esp的值再函数调用中保存。它的实现过程就是通过上图中的这些程序这里我们就不展开讨论了知道有这么个东西就行 接下来我们继续执行就会发现此时 esp 和 ebp 指向的地址都发生了变化此时ebp指向的地址大家还有印象吗我们来看一下它们此时分别指向的是哪里 从图中我们可以看到原来刚刚释放的空间是存放main函数的栈底地址的空间在这个空间被释放后esp指向了存放0x00c418f7这个地址的空间下一步就是通过ret指令返回main函数了。 大家还记得0x00c418f7这个地址的含义吗我们继续执行来看一下此时函数会返回到哪里 可以看到此时又有一块空间被释放了函数在经过ret指令后返回的地方就是刚才释放的空间中存储的地址也就是call指令的下一条指令的地址。 大家现在有没有发现汇编语言的逻辑的严谨之处。 在调用Add函数之前先通过call指令将下一条指令的地址存放在一个新的空间中这是为了在调用结束后能够通过ret正确返回而进行的一步操作在创建Add函数之前通过push指令将main函数的栈底地址存放在新的空间中这样我们在调用完函数后能够通过pop指令在释放Add函数的函数栈帧时找到main函数的栈底地址。 以上就是函数栈帧销毁的整个过程通过pop、ret指令来释放函数栈帧的空间。现在已经回到主函数了我们还有一个问题没有解决Add函数的值是如何返回到main函数中的下面我们接着观察 九、函数的返回值 在回到main函数后我们可以看到此时的程序需要执行两个指令 00C418F7 add esp,8 //将esp增加8 00C418FA mov dword ptr [ebp-20h],eax//将eax的值赋值给[ebp-20h]我们来看一下这两步的作用是什么 此时还未执行这两步操作我们可以从图中观察到此时的 esp 指向的空间是形参x的空间通过 esp 8我们可以得到的地址是存放0x00c41023的空间地址而mov操作中的 ebp - 20h找到的地址是变量c的地址。 此时函数栈帧的情况如下所示 我们现在可以对这两步做个小结 经过add这一步程序将形参的空间给释放了再经由mov这一步将存储在 eax 中的值成功赋值给了变量c。对于Add函数而言它所谓的返回值其实并不是从局部变量z中进行返回的而是通过 eax 这个寄存器对操作的值进行临时记录从而达到返回值的效果。所以在Add函数中对于这个局部变量c是可有可无的这也解释了为什么我们可以在定义Add函数时可以直接写成return x y;这种形式了因为此时的返回值是直接存储在寄存器 eax 然后回到main函数后再使用 eax 存储的值而不是存放在局部变量中将局部变量的值返回给主函数了。 那到目前为止函数栈帧的创建与销毁过程我就全部介绍完了后面还有涉及到printf函数的调用与main函数的函数栈帧的销毁我就不过多叙述了。如果各位还有何疑问的话可以在评论区留言哦 结语 今天的内容到这里就全部结束了本篇内容是函数篇章的一个补充知识点这一部分内容对各位在C语言学习的理解上也会有很大的帮助。感兴趣的朋友可以自己下去后按照文章内容从头到尾一步一步的走一遍这样你能够更加容易理解文章中对于一些知识点的描述。 喜欢本文的朋友可以点赞、留言、转发来支持一下博主。最后感谢各位的翻阅咱们下一篇再见 “存储程序”的概念将指令以二进制代码的形式事先输入计算机的主存储器中然后按其在存储器中的首地址执行程序的第一条指令以后就按该程序的规定顺序执行其他指令直至程序执行结束。 ↩︎ 其实寄存器就是一种常用的时序逻辑电路但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的因为一个锁存器或触发器能存储1位二进制数所以由N个锁存器或触发器可以构成N位寄存器。 ↩︎
http://www.sadfv.cn/news/142510/

相关文章:

  • 北京网站建设外包公司哪家好各类专业网站建设
  • 网站被墙网易企业邮箱后缀
  • 可以做兼职的网站有哪些thinkphp 网站模板
  • 常熟建设局网站怎么把自己做的网页生成链接
  • 做英语quiz的网站手机网站建设官网
  • 做网站需要跟客户了解什么深圳做企业网站的
  • 网销的网站建设与管理广州网站建设菲利宾
  • 贷款公司网站模板网络app推广是什么工作
  • 辽宁建设厅规划设计网站网站后台怎么打开
  • 西双版纳建设厅网站电商网站 开发周期
  • 学校网站建设发展概况分析企业微信网页版
  • 建设ftp网站怎么创建数据库设计师网名 二字
  • 桂林卖手机网站网站设计建设
  • 成都建站免费模板久免费域名注册网站
  • 大学生做网站步骤dedecms免费模板
  • 网站建设宣传册内容wordpress插件的意义
  • git怎么做隐私政策网站中信建设有限责任公司营业执照
  • 网站建设越秀上海建站网站
  • 网站 做百度推广有没有效果电商网站怎么制作
  • 苏州网站开发建设电话第一活动线报网
  • aspcms网站新型实体企业100强
  • 烟台做网站系统深圳网站搭建找哪里
  • 贵州省城乡建设局网站查适合用于网站开发的工具
  • 深圳设计网站费用京网站建设
  • 成都开网站做一份网站的步zou
  • 南宁市兴宁区建设局网站法律咨询网站建设方案
  • 锦州网站seoit外包主要做什么
  • iis 发布网站 500js网站一键变灰
  • 深圳专业网站建设多少钱福建省建设厅网站劳保核定卡
  • 北京网站 百度快照东莞seo建站推广