目录
此时如果没有给a变量赋值,那么该位置的值就是CCCCCCCC,这就是为什么未初始化的局部变量默认为随机值的原因。
执行call指令后,call指令还会将它下一条指令的地址压栈:原因是因为,当我们把调用的函数执行完后,我们需要回到main函数,就会通过这个地址回来。
此时我们就可以理解为什么函数调用结束后,会把局部变量给销毁,但也可以把值传出来,因为值是放在了eax寄存器里面保存起来了。
此时执行这条指令就会返回到刚开始call指令的下一条指令 :
(1)、首先要注意函数栈帧在不同的编译器下面都有所差异
(2)、首先我们需要认识几个寄存器eax、ebx、ecx、edx、ebp、esp。这些会在后续讲解中出现,其中最重要的就是ebp和esp。
(2)、ebp、esp里面存放的是地址,esp存放低地址,ebp存放高地址,,这两个地址是用来维护函数栈帧的。
一、ebp和esp
下面用一段简单的代码来介绍:
int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 10; int b = 20; int c = 0; c = Add(a, b); return 0; }
注意每个函数调用,都要在栈区创建一个空间:
当前我们在调用main函数,所以在栈区开辟了一块空间,esp就指向这块空间的低地址,ebp指向这块空间的高地址用于维护这块空间,在调用哪个函数,这两个寄存器就去维护哪块空间。又因为栈区的空间是从高地址向低地址使用的,所以esp被成为栈顶指针,ebp被称为栈低指针。
二、main函数的函数栈帧(解释反汇编)
首先我们看到要知道在vs2013中main函数还会被其他函数调用:
然后我们来看main函数的反汇编:
刚开始还在是调用main函数那个函数的栈帧:
(1)、push操作
即压栈,意思是把ebp压入栈顶。
(2)、mov操作
可以理解成赋值,即将esp赋值给ebp指针,这样ebp也指向esp栈顶
(3)、sub操作
即减操作,意思是将esp减去0E4h(这是十六进制数字)地址,因为栈区是高地址向低地址使用,所以esp会往上走指向某个地址。
此时紫色这块空间其实就是为main函数申请的空间(main的栈帧)
(4)、然后又是三个push操作
注意push有个动作,push后esp指针会跟着动,所以push完后如下:
(5)、lea操作:
加载有效地址,即将后面那个地址放进edi里面去
(6)、接着四步操作:
更改相应指针的值后,将edi指向的地址向下39h个位置,每dword为一个单位(dword叫双字,即四个字节)的值全部改为0CCCCCCCCh的值。(刚好等于刚刚给main函数开辟的栈帧空间)
此时给main函数开辟栈帧这个准备就准备完了。
三、如何创建的变量(创建变量的反汇编)
main函数栈帧准备完后就开始创建变量了:
(1)、第一个int a = 10:
是将0Ah(十进制就是数字10),mov(移动或赋值)到[ebp-8]的位置,即main函数栈帧中的一个位置:
此时如果没有给a变量赋值,那么该位置的值就是CCCCCCCC,这就是为什么未初始化的局部变量默认为随机值的原因。
(2)、变量b、c创建的过程同理:
四、函数调用、传参过程
(1)、对变量b操作
首先把ebp-14h里面的值放进eax,即把变量b的值20放进eax,然后将eax压栈,即将20压栈,因为push操作,注意esp也会做出相应移动:
(2)、对变量a操作
这两条指令与上面同理:
(3)、call操作,即调用,此时按快捷键f11进入
执行call指令后,call指令还会将它下一条指令的地址压栈:原因是因为,当我们把调用的函数执行完后,我们需要回到main函数,就会通过这个地址回来。
接着再按下f11就会进入被调用函数的栈帧
五、被调用函数的栈帧
通过上述步骤后就来到了被调用函数的栈帧:
我们会发现前面准备工作的几条指令和main函数栈帧都差不多,在为该函数分配内存开辟空间:
(1)、变量的相加
因为前面操作都Main一样,所以直接跳到变量相加的地方,此时形参x,y就出现了
结合上面我们知道ebp+8就是变量a传参的位置:
同理ebp+0ch(ebp+12)就是变量b传参的位置:
然后将相加的值(add操作)放到ebp-8的位置,即赋值给变量z。
此时我们就可以理解:形参是实参的临时拷贝
六、函数返回值的过程
标签:满满,函数,esp,C语言,地址,ebp,main,栈帧,变量 From: https://blog.csdn.net/hffh123/article/details/143641196(1)、第一个操作,把ebp-8(即z的值(返回值))转移到eax寄存器。
此时我们就可以理解为什么函数调用结束后,会把局部变量给销毁,但也可以把值传出来,因为值是放在了eax寄存器里面保存起来了。
(2)、pop:弹出出栈
出栈后,esp指针会跟着向下移动,此时把三个寄存器给出栈了
(3)、回收函数空间:
到这就开始回收空间了,把ebp的值给esp:
根据上述,我们知道ebp在Add函数栈帧的下面:
然后又执行pop指令:
把ebp给pop了,而这个位置我们放的是main的ebp地址,所以把这个ebp弹出后,ebp指针就会找到main函数的ebp地址去,然后有pop操作,esp也会移动。
此时就彻底回到main函数的栈帧了
(4)、ret操作:
此时执行这条指令就会返回到刚开始call指令的下一条指令 :
在继续执行add指令,此时形参就销毁了