void func(int a, int b)
{
int c;
c=a+b;
}
int main(void)
{
func(3,4);
return 0;
}
编译后的汇编代码如下图所示,当然main函数也是一个调用函数,但是我们不看它,直接看调用func函数的汇编代码。从第11行开始,首先,从最后一个表格看一下压栈前的ebp和esp的值,ebp=0x0012ff80,esp=0x0012ff34(ebp为栈基址指针,esp为栈顶指针,压栈和出栈改变的是esp,压栈方向向下,也即地址减少),然后开始对参数3和4进行压栈,压栈顺序从后往前,接着调用call指令进入函数体(第2行),这里函数的返回地址将自动压栈;接着进入函数后,前两条指令push ebp和mov ebp, esp将改变堆栈结构,为什么要采用这种方式还不太明白,接着开始执行函数中的代码。执行完成后,弹出堆栈中的内存,然后又分别调用mov ebp, esp和push ebp回复最先的堆栈结构,最后执行ret退出函数(最先返回地址自动弹出)。
总结:
1. 对于X86CPU的堆栈生长方向是地址递减的方向。
2. 函数调用时参数的压栈顺序是逆序的,最后一个参数先压栈,第一个参数最后压栈。
3. 进入函数后,会对原有堆栈结构进行重新调整,首先将ebp压栈;然后将ebp指向当前的esp,也就是改变栈底指针,并且还改变了esp栈顶指针的值,就好像从新开辟了新的堆栈结构;接着在新的堆栈上进行函数代码实现;最后恢复原先栈结构。
4. call指令会自动将返回地址(eip)压栈,ret指令也会自动将返回地址(eip)出栈。