绪论

书接上回,上回我们已经将C语言的所有知识点进行了总结归纳到同一个思维导图中,而本章是一个番外篇,将具体讲述一些更深入的知识。

话不多说安全带系好,发车啦(建议电脑观看)

附:红色,部分为重点部分;蓝颜色为需要记忆的部分(不是死记硬背哈,多敲);黑色加粗或者其余颜色为次重点;黑色为描述需要

思维导图:

要XMind思维导图的话可以私信哈

目录

1.函数栈帧的创建和销毁

1.1寄存器

1.2创建函数栈帧

1.3程序进程中的汇编代码

1.4函数栈帧的销毁

2.到了此处我们需要学习到的是:

2.1变量是如何创建的创建?

2.2为什么变量不初始化会是随机值?

2.3函数是怎么传参的以及传参的顺序是什么?

2.4形参和实参的关系是?

2.5函数的调用时怎么实现的?

2.6调用结束后是怎么返回的?


1.函数栈帧的创建和销毁

函数栈帧的创建和销毁在不同的编译器底下的实现都不一样(越高级的编译器越复杂,但其基本逻辑是一样的)

1.1寄存器

知识点:

寄存器是具有存储功能的一个cup内的原件

在C语言中有着多个寄存器(eax、ebx、ecx、edx、ebp、esp)但各自有着不同的功能

对于函数栈帧的创建和销毁主要要看的是下面这两个寄存器

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

细节(注意点):

  1. 每一次函数的调用过程中都会在栈帧上开辟一块空间,对于每个开辟的空间就被称作该函数的函数栈帧(如ADD函数的函数栈帧)
  2. 栈区空间的使用习惯是先使用高地址再使用低地址
  3. main主函数也是被其他函数调用的(当进入到主函数时esp、ebp就会指向主函数所借用的空间(指向正在进行的函数)具体细节我们就不再过多的追溯了)

1.2创建函数栈帧

知识点:

当进入到调用的函数的函数栈帧他会

  1. 先进行push压栈,并且把寄存器ebp指向所压的空间
  2. 然后再将esp指向和ebp一样的位置(具体看下图)
  3. 再将esp减去将要开辟的空间大小(每个函数减的大小(所开辟的空间大小)都可能不一样这取决于编译器。附:因为函数栈帧的使用习惯是先使用高地址再使用低地址的所以是减才能让寄存器所指向的位置往上走)(具体看下图)
  4. 再然后会连续push(压栈)三个寄存器进来ebx、esi、edi(这具体的细节不用去了解)
  5. 最后的4个个步骤可以概括为将所开的空间(从esp到ebp)全部置为cc cc cc cc(具体看下图)

1.3程序进程中的汇编代码

知识点:

1.当成功创建了一个栈帧后,我们对栈帧内的代码执行,此处先对int a ,b 的初始化

具体是在ebp-8和ebp-20的位置进行mov操作,将2存放到ebp-8,将3放到ebp-20(所以说变量的创建是从栈帧中的ebp-8处开始创建第一个的,并且每次开辟的空间和上一次变量开辟的位置中间相差2个整形的空间,地址相差12)(具体看下图)

2.再到ADD函数的调用

对于前四步可以概括为从右往左的把参数进行压栈操作(将参数压栈到栈顶)(具体看下图)

3.call调用该函数,并且记住call指令后的下一条指令的地址进行压栈,后面可以通过压栈内的地址就可以找回然后继续往下进行程序(具体看下图)

1.4函数栈帧的销毁

知识点:

当我们进入到ADD函数后,要经历和main函数一样的函数栈帧创建过程:

具体我就不再讲述了和main函数一样的操作(ebp压栈、改变esp、再ebx esi edi压栈、再将开辟的空间赋值成cccccccc)

细节:

进入到ADD函数的语法部分

1.首先对z的初始化

第一个变量的位置ebp-8处存放z = 0(具体看下图)

2.其次进行z = x + y 的赋值

先将之前压好的数据(ebp+8)移动到寄存器eax上

再加上另一个之前压好的值(ebp + c处的数据( c是十六进制就是表示ebp + 12))

最后再把这个寄存器上的值存进z = 5(具体看下图)

3.最后返回

再把z位置上的值(ebp-8)存放到eax上(如果不存的话当栈帧的销毁会导致数据的丢失)(具体看下图)


说了这这么多上面都是在讲述一些汇编代码,

下面才是一个栈帧的销毁

  1. 首先将edi esi ebx 这些之前压栈的进行出栈操作(具体看下图)
  2. 然后把esp移动到ebp所指向的位置(ADD的栈底)
  3. 然后后因为ebp的出栈,所以会让ebp返回之前函数的底部(因为一开始对main函数的ebp进行了压栈,会把压栈里的值附给此处的ebp)
  4. 然后esp因为ebp的出栈就会指向一开始存好的call指向下一条指令的地址所压的栈上
  5. 当进行ret会把该处进行pop处理所以esp就会指向之前压栈的两个形参ecx、eax,而程序会得到了call指令的下一条指令的地址就会接着从下一条指令开始往下执行
  6. 回来后再对esp + 8 就会重新指回main函数的顶部

最后再把存放在eax中的z的值存放到c开辟的空间处

后面再进行printf的打印以及mian函数栈帧的摧毁即可

对此就不再过多的追究了都是一些汇编代码的问题以及和ADD函数一样的栈帧的摧毁问题


2.到了此处我们需要学习到的是:

2.1变量是如何创建的创建?

先通过ebp、esp两个寄存器进行压栈和esp的减去一定的值再对该区初始化后在这开辟好的栈帧空间内分配一定空间给该局部变量

2.2为什么变量不初始化会是随机值?

因为在局部变量放进去之前,已经对该区域的数值进行了随机的存放,如果该变量没有初始化,那么该变量的值就等于当初随机存放好的值(cc cc cc cc)(所以我们应该初始化把随机进行覆盖)

2.3函数是怎么传参的以及传参的顺序是什么?

当传参时是将参数从右向左的把参数进行压栈压到栈顶,后面直接从ebp+8 ….. 进行取数据

2.4形参和实参的关系是?

因为是压栈的,压栈就意味着开辟一块新的空间只是把实参的数据拷贝了过来,所以说在传值调用时改变形参不影响实参

2.5函数的调用时怎么实现的?

之前的call、ret来进行的(具体上面已经写过)

2.6调用结束后是怎么返回的?

通过寄存器eax进行保存返回的结果

持续更新细致内容,三连关注哈