首先我们必须知道:
一个函数调用另外一个函数是将数据(过程参数和返回值)和控制从代码的一部分传递到另外一部分。包括为被调用的函数的局部变量分配内存空间并在退出时释放这些空间。其中,数据的传递,局部变量的分配和释放是通过操纵程序栈来实现的。
程序栈都是存放在内存的某个区域,而且栈都是向下增长的,所以,栈顶的元素的地址是所有栈中元素地址中最低的。
- 寄存器ebp(base pointer )可称为“帧指针”或“基址指针”,其实语意是相同的。
- 寄存器esp(stack pointer)可称为“ 栈指针”。
- 帧指针%ebp 在未受改变之前始终指向栈帧的开始,也就是栈底,所以%ebp的用途是在堆栈中寻址用的。
- 栈指针%esp 保存着栈顶元素的地址,是会随着数据的入栈和出栈移动的。
- 指令指针IP是一个16位专用寄存器,它指向当前需要取出的指令字节,当BIU从内存中取出一个指令字节后,IP就自动加,然后指向下一个指令字节。
一般来说,寄存器%eax,%edx和%ecx用于调用函数保存数据;寄存器%ebx,%esi和%edi用于被调函数保存数据
以下面的函数调用为例:
1 int swap(int *p1, int *p2 ) 2 { 3 int x= *p1; 4 int y= *p2; 5 *p1=y; 6 *p2=x; 7 return x+y; 8 }
1 int caller( ) 2 { 3 int result=swap(int arg1,int arg2); 4 return result; 5 }
1, 在调用f之前,将传入的参数arg1,arg2都压入堆栈中
2, 把函数f的执行后的返回地址压入堆栈,然后指令指针IP指向函数f的地址,开始执行f
3, 对于执行f这部分,编译成汇编代码后主要包括三个部分:初始化栈帧;执行过程;恢复栈的状态和函数返回。
其汇编代码如下:
初始化帧栈: swap: push %ebp //先将调函数caller的堆栈的基址(ebp)入栈,以保存之前任务的信息 mov %ebp, %esp //然后将调用函数caller的栈顶指针(esp)的值赋给ebp,作为新的基址即被调函数swap的栈底 push %ebx //保存ebx 进入swap函数体: mov %edx, 8(%ebp) mov %ecx, 12(%ebp) mov %ebx, (%edx) mov %eax, (%ecx) mov (%edx), %eax mov (%ecx), %ebx add %ebx, %eax Set 函数返回: pop %ebx mov %esp, %ebp //从当前栈帧的ebp即恢复为调用函数caller的栈顶%esp,使栈顶恢复函 数B被调前的位置 pop %ebp //然后调用函数caller再从恢复后的栈顶弹出之前的%ebp值 ret
栈示意图:
这样函数swap的调用就结束了,caller继续运行。