- 局部变量是怎么创建的?
- 为什么局部变量的值是随机值?
- 函数是怎么传参的?传参的顺序是怎么样的?
- 形参和实参是什么关系?
- 函数调用是怎么做的?
- 函数调用结束后是怎么返回的?
这些和函数栈帧的创建和销毁有关,下面会慢慢解答:
对于函数栈帧的创建和销毁,编译器越高级,越不容易学习和观察。
函数栈帧的创建和销毁的过程在不同的编译器上是略有差异的,大体逻辑是一致的,具体细节取决于编译器的实现。
正在调用哪个函数,ebp和esp维护的就是哪个函数的函数栈帧
在main函数调用的过程中:
(压栈push是给栈顶放一个元素,出栈pop是从栈顶删除一个元素)
push是压入元素
push完esp就会变化
然后esp的指向就会变成下图
move是把esp的值给ebp
sub是减,给esp减去0E4h
esp变小,也就是esp指向了上面的区域
push了 ebx esi edi之后
lea加载有效地址,相当于给edi里加了个地址
把39h放到ecx,eax里面放的是0CCCCCCCCh这样的值
然后下面的那句话的意思是:要把从edi这个位置开始向下的39h个dword(double word)(一个word就是2个字节,dword就是4字节)的数据全部改成eax的内容
每次操作4个字节,操作39h次,然后改成eax的内容
上面main函数的栈帧已经开辟好,然后准备执行有效代码
把0Ah这个十六进制数字(0A也就是十进制10)放到ebp-8的位置
如果没有对a初始化,那么默认就是那些CCCC…
之前我们经常能打印出随机值烫烫烫烫,就是因为内存中放的是CCCC…这样的值,不同编译器上放的随机值不一样,所以变量最好进行初始化
把14h这个十六进制数字放到ebp-14的位置
空了两个整形的位置
别的编译器可能紧挨着放,因为这个空出的大小取决于编译器
把0放到ebp-20h的位置
在函数中局部变量是怎么创建的?
首先为我这次的函数调用创建函数栈帧,然后在它的函数栈帧里找到空间把a,b,c什么的放进去。
当函数调用时
把ebp-14h里的值放到eax里,就是把20放到eax里面
然后push eax
把ebp-8里的值放到eax里,就是把 a(10)放到ecx里面
然后push ecx,把10压到顶上
call是调用函数,先记住call的地址
调用之后:
00 c2 14 50
call这个动作把call的下一条指令的地址压到上面了
记住这个地址的原因是,call之后去调用add函数,跳到add函数里之后,调用完add函数之后还需要返回来,要回哪去?回到call指令的下一条指令的位置,然后再从这个地址往下执行
进入函数之后:
和main函数开辟函数栈帧的过程一样,是为add函数准备函数栈帧
push ebp之后:
move 把esp的值给ebp
然后sub esp减去0CCh,esp就指向了上面的空间
然后push 三个数:
然后往下就是像main函数一样把空间中的内存初始化为CCCCCCCC
准备执行计算
z的创建:把0放到ebp-8的位置
ebp+8找到ecx中的10放到eax
然后把eax中的20加到eax
eax中的值此时就为30了
然后把eax中的值放到ebp-8的位置,也就是z的位置
函数在调用计算的时候,形参不是主动创建的,是因为在调用函数的时候就把参数传过去了,参数从右向左传的
形参根本不是在add函数内部创建的,而是回来找了在调用的时候传参压的那块空间
有这样一句话:形参是实参的临时拷贝,在这里也得到验证
这时候到返回部分了:
把[ebp-8]的值放到eax里(也就是把z的值放到eax里),eax是一个寄存器,不会因为程序退出就销毁。因为z出函数后销毁后值就不在了,所以暂时把z的值放到寄存器里,等回到主函数再把eax的值拿出来用就可以了。
三次pop之后:
esp指向的位置就发生了改变
回收空间操作:把ebp赋给esp
esp就指向下面去了
然后pop ebp把栈顶元素弹出
栈顶元素存的是main函数的ebp也就是下图位置的地址
main-ebp存在栈顶的原因是:在函数调用返回之后随着函数栈帧的销毁,main函数的栈顶容易找到,但是main函数的栈顶就不记得了,所以先把main函数的栈顶存在main-ebp。
pop弹出把main-ebp的值弹到ebp,ebp就重新指向了main函数栈底的位置,同时esp往下,回到了main函数
main函数这块空间又由ebp和esp维护了
从main函数回来之后,我们应该从call指令的下一条指令的地址往下执行
ret指令的作用就是:return返回的时候,这个指令把esp指向的那个call指令的下一条指令的地址弹出并且跳到那个位置
esp+8之后就把x,y形参的空间释放了
把eax寄存器中的值放到ebp-20h,也就是放到c中
c=Add(a,b)返回值这时候带回来了