学习目标
1.局部变量是如何创建的?
2.为什么局部变量的值是随机值?
3.函数是如何传参的?传参的顺序是怎样的?
4.形参和实参是什么关系?
5.函数调用是如何做的?
6.函数调用结束后是怎么返回的?
目录
1.寄存器
寄存器是CPU的组成部分之一,在CPU中,其对数据的读取能力是最快的,不需要IO运输,但也决定了其价格的昂贵和数量的稀少,稀少到几乎每个寄存器都有着其自己的名字,甚至是多个名字.
通用寄存器
eax 累加和结果寄存器
ebx 数据指针寄存器
ecx 循环计数器
edx i/o指针
esi 源地址寄存器
edi 目的地址寄存器
esp 堆栈指针
ebp 栈指针寄存器
在接下来介绍具体过程中,会涉及寄存器的名称,不需要了解它具体是如何实现的,只需要明白它是寄存器,并且进行了哪些操作就好.
下面是代码展示的部分
下面我们会对这段代码进行深入了解,从而了解函数栈帧创建和销毁的具体过程
2.具体过程
1.首先必须明确几个基本知识:
(1)函数调用,创建变量,数组等等都需要在栈区申请一片空间,并且符合由高地址向低地址创建的原则.
(2)main函数也是由其它函数调用的,不过在VS2019等编译器下不好进行观察,采用VS2013可以在调试中调用堆栈中观察到调用,mainCRTStartup调用了_tmainCRTStartup,而 _tmainCRTStartup调用了main函数
(3)ebp(bottom),esp两个寄存器是用来存放地址的,这两个地址都是用来维护函数栈帧的
(4) 每次push操作,esp都会进行相应的移动,始终指向栈顶
2.接下来我们就可以开始了解具体过程了
我们采取在vs中调试中反汇编的方式进行了解,下面是涉及反汇编的一些指令解释
push ------->压栈 将某块空间压到栈顶
mov ------->移动 将指针移动位置,可以指定移动的位置
sub ------->减法 这个比较好理解,就是将指针从高地址移动到某个低地址处
lea --------> load effective address 加载有效地址,给edi存进去一个地址,一般和其它操作配合,对新生成的空间进行随机初始化所用的
call -------->执行这条指令的时候,把call指令的下一条指令的地址进行压栈操作(目的:在函数调用结束的时候,能够回到call指令的下一条指令地址处)
首先是调用main函数,也就需要申请空间,并进行初始化,如下图所示
在一系列操作后,main函数就基本完成初始化了,如下图所示
然后是进行相应在main函数里面的操作,通过移动ebp,指向main函数里面不同的空间,初始化不同的值,也就是我们常说的为变量开辟一块属于它们的空间.
紧接着是调用我们设计的Add函数具体过程
通过监视,我们也可以发现,eax,ecx的值已经改成a,b变量的值,在main函数上面的某块区域,已经产生了a,b的一块形参空间,esp已经指向栈顶的那块空间,如图所示
之后进入我们的Add函数的栈帧创建,整个过程和main函数栈帧的创建基本一致,这里不再叙述
在Add函数栈帧中,创立z变量的空间,在eax寄存器中完成加法操作,然后会将值放在eax寄存器中返回回去,毕竟我们知道一旦出了函数,原本的空间就要归还给电脑.
结果存在eax寄存器后,将要返回空间了,通过对Add函数空间返回过程,我们也可以类似得到main函数的栈帧销毁过程.
3.总结
至此,我们已经基本了解函数栈帧创建与销毁的过程
简单概括,可以说为
两个指针ebp,esp始终维护函数栈帧,通过移动esp我们可以创建函数栈帧,esp始终指向栈顶,每次创立函数栈帧push ebx,esi,edi,都会相应移动,pop操作也不例外
移动ebp,可以在栈帧中创建变量,可以通过pop,保证ebp始终维护函数栈底
临时变量的创建发生在进入函数,开辟函数栈帧之前,并且符合从右往左创立的原则.