首先先提出下面五个问题:
(1)形参开辟内存吗?由谁开辟?
(2)形参的入栈顺序?
(3)返回值如何带出?
(4)被调用方结束后如何知道回退到调用方栈帧上?
(5)函数调用完成如何知道执行下一行指令?
要解决这些问题,我们就要从汇编的角度切入。通过汇编代码能够使我们更加清晰地掌握函数的堆栈调用。 汇编分为两种形式inter x86 (从右向左看) 和 AT&T unix(从左向右看),我们学习的主要是inter x86下的汇编代码。
1、先来了解一下反汇编的一些指令
- 移值:mov
int a = 20;
inter x86 (从右向左看) mov dword ptr [a] , 14h;
AT&T unix(从左向右看)mov 14h , dword ptr [a];
都是指将十六进制的20 放在dword ptr [a]中;
- 移地址:led
led eax ,[ebp-4] 将ebp-4的地址传送到eax中;
- 压栈:push
push 10; 将10压入栈中
- 出栈:pop
pop eax ; ==> eax = pop();
- 累加指令:add
add eax, 4; ==> eax+=4;
- 累减指令:sub
sub eax, 4; ==> eax-=4;
2、寄存器
存储数据:eax、ebx、ecx、edx.
ebp:栈底指针寄存器
esp:栈顶指针寄存器
pc:下一行指令寄存器
举例:
进入Debug版本查看该程序的InterX86反汇编代码如下:
有反汇编代码解释可以回答问题
(1)形参开辟内存吗?由谁开辟?
形参开辟内存,由于这是在main函数的栈帧上,所以是由被调用方开辟。
(2)形参的入栈顺序?
形参的入栈顺序是自右向左。
(3)返回值如何带出?
<=4字节返回值由eax寄存器带出;
4字节<返回值<=8字节时,由eax和ebx寄存器共同带出;
>8字节,由临时量带出。
这是sum函数调用返回值之后的一些操作:
(4)函数调用完成后如何知道回退到调用方栈帧上?
call指令会把调用方的下一行指令地址压栈,在压入调用方栈底地址。在跳转到被调用方函数栈帧。pop ebp就是将压入的栈底指针弹出,会退到main函数栈帧上。
(5)函数调用完成如何知道执行下一行指令?
当调用结束时回退到调用方栈底,再执行ret指令,1、pop pc将值 pop出并赋值个PC寄存器,即弹出下一行指令地址,2、add esp 8 销毁形参。
总结:
开栈:
- 压入实参为形参始化自右向左;
- 压入下一行指令地址;
- 压入调用方栈底地址;
- 跳转到被调用方栈帧;
- 被调用方开辟活动空间并初始化。
清栈:
- 栈开辟空间清空;
- 弹出调用方栈底地址;
- 回退到调用方栈底;
- 将下一行指令存放进PC;
- 清理形参内存。