2.创建变量
在代码的反汇编部分,右击鼠标,将显示符号名关掉
一直按F10到Add之前的汇编代码处
mov dword ptr [ebp-8],5 将5存入 ebp-8 的位置
mov dword ptr [ebp-14h],2 将2存入 ebp-14h(4+1*16=20(ebp-20))的位置
mov dword ptr [ebp-20h],0 将0存入 ebp-20h(0+2*16=32(ebp-32))的位置
存储之后的内存图这样就创建了变量a,b,c
3.函数传参
mov eax,dword ptr [ebp-14h] 将ebp-14h(其实就是b的值)的值放入eax(寄存器)里面去
push eax eax压栈,将eax放在栈顶
mov ecx,dword ptr [ebp-8] 将ebp-8(其实就是a的值)的值放入ecx(寄存器)里面去
push ecx ecx压栈 ,将ecx放栈顶
传参---先传b,再传a (说明函数传参是从右向左的)
4.函数调用
call--函数调用
执行到此处要按F11进入函数内部
观察内存后,发现执行完call后,call指令的下一条指令的地址被压栈
当后面执行完Add函数后,通过这个地址可以找到调用位置并继续向下执行代码再按F11就进入了Add函数,发现前面的指令与main函数里面一样,说明函数栈帧的创建过程是相同的
与刚开始创建mian函数栈帧的过程相同,唯一不同的就是不同函数栈帧开辟的空间大小有差异,这个空间大小取决于编译器
同样,由于正在调用Add函数,ebp和esp就来维护Add函数的函数栈帧
同样,在Add函数中开始执行代码mov eax,dword ptr [ebp+8] ebp+8的值赋给eax
add eax,dword ptr [ebp+0Ch] 将ebp+0Ch的值与eax的值相加并赋值给eax
mov dword ptr [ebp-8],eax 将eax的值赋给ebp-8
eax是一个寄存器,可以存储变量/变量的值
说明真进行函数调用的时候,形参并不是在Add函数内部创建的,而是在函数调用刚开始的时候,通过压栈,将参数a,参数b传上去了,且参数b是先传的
在使用形参的时候,回头找到了以前压栈的这块空间,说明形参确实是实参的一份临时拷贝
形参并没有真再创建自己的空间
最后将形参计算的结果存入ebp-8中
但是如果在函数里面创建变量的话,它的内存是单独分配的
四、函数栈帧的销毁
先将计算结果ebp-8(z)里面的值存在寄存器中,方便后面将Add的函数栈帧销毁后返回
三次pop,从栈顶删去三个元素
将ebp赋为esp(说明esp移动到ebp位置,此时栈顶发生变化,到了ebp的位置)
然后从栈顶删去一个元素放在ebp里面,esp继续向下。
因为此时栈顶放的是main函数的栈底地址,所以,ebp通过这个地址找到了main函数的栈底
此时就从Add函数里面顺利的回到了main函数里面
因为00F83420是调用函数的指令call的下一条指令的地址,所以恰好可以让代码继续运行
这就是上面为什么要将call下一条指令的地址存起来
确保‘走出去’,还能‘找回来’ 继续运行add这一条指令
然后ret(return):将栈顶的那个指针pop(出栈)
esp(栈顶指针)再向下移动此时esp+8,即将栈顶 + 8 ,就相当于将形参x,y销毁,如图
然后mov 将寄存器eax中存的Add函数的计算结果放在ebp-20h中,即c中
此时要进入printf函数,虽然printf已经封装好了,但printf函数的调用还是要经历函数栈帧的创建和销毁这个过程,方法与Add函数相同,这里就不过多赘述
那么return 0就是main函数栈帧的销毁过程
最后的结果就是main函数的栈帧也被销毁