C4-C语言语句的机器级表示
C语言语句的机器级表示
过程调用的机器级表示
- 参数通过栈来传递.
- EBP:帧指针寄存器(指向当前栈帧的底部). ESP:栈指针寄存器(指向当前栈帧的顶部)
- 栈帧: 每个过程独自占据的栈的部分. 每调用一个过程都会创建一个新的栈帧. 一个栈可以由多个栈帧组成.
调用过程
P为调用者,Q为被调用者
(1)P将入口参数(实参)放到Q能访问到的地方;
(2)P保存返回地址,然后将控制转移到Q;(CALL指令)
(3)Q保存P存储在通用寄存器的内容,并为自己的非静态局部变量分配空间;
(4)执行Q的过程体(函数体);(处理阶段)
(5)Q恢复P存储在通用寄存器的内容,释放局部变量空间;
(6)Q取出返回地址,将控制转移到P。(RET指令)
P过程: 12. Q过程:3456. 准备阶段:3. 处理阶段:4. 结束阶段:56.
所有过程共享同一套通用寄存器
过程调用中栈和栈帧的变化(Q:被调用过程)

IA-32寄存器使用规定
调用者负责保存的寄存器:EAX、EDX、ECX
当过程P调用过程Q时:
- Q可以直接使用这三个寄存器,不用将它们的值保存到栈中。
- 如果P在从Q返回后还要用这三个寄存器的话,P应在转到Q之前先保存,并在从Q返回后先恢复它们的值再使用。
被调用者负责保存的寄存器:EBX、ESI、EDI
- Q必须先将它们的值保存到栈中再使用它们,并在返回P之前恢复它们的值。
EBP和ESP分别是帧指针寄存器和栈指针寄存器(都是专用的寄存器),分别用来指向当前栈帧的底部和顶部。
为减少准备和结束阶段的时间开销,每个过程应先使用EAX、ECX、EDX!
过程(函数)的结构
准备阶段
- 形成帧底:push指令 和 mov指令
- 生成栈帧(如果需要的话):sub指令 或 and指令
- 保存现场(如果有被调用者保存寄存器) :mov指令
过程(函数)体
- 分配局部变量空间,并赋值
- 具体处理逻辑,如果遇到函数调用时
- 准备参数:将实参送栈帧入口参数处
- CALL指令:保存返回地址并转被调用函数
- 在EAX中准备返回参数
结束阶段
- 退栈:leave指令 或 pop指令.
- 取返回地址返回:ret指令
leave指令具体:
movl %ebp %esp
popl %ebp
示例

caller:
pushl %ebp 将(%ebp)入栈
movl %esp, %ebp 将(%esp)内容赋给(%ebp)
subl $24, %esp 将(%esp)减24
movl $125, -12(%ebp) 将125赋给(%ebp-12)
movl $80, -8(%ebp) 将80赋给(%ebp-8)
movl -8(%ebp), %eax 将(%ebp-8)的内容赋给(%eax)
movl %eax, 4(%esp) 将(%eax)内容赋给(%esp+4)
movl -12(%ebp), %eax 将(%ebp-12)的内容赋给(%eax)
movl %eax, (%esp) 将(%eax)的内容赋给(%esp)
call add 调用add函数
movl %eax, -4(%ebp) 将(%eax)的内容赋给(%ebp-4)
movl -4(%ebp), %eax 将(%ebp-4)的内容赋给(%eax)
leave
ret 退栈(帧)释放
注
返回参数总在 %eax中.
每个过程开始总是: pushl %ebp, movl %esp %ebp
对于pushl %ebp; movl %esp, %ebp指令的解释:
pushl %ebp. 将旧的EBP寄存器的值(上一个过程的栈底的值)压栈, 这样就在上一个过程的栈帧后面开辟了一个新的区域存放EBP的旧值. 由于EBP寄存器存放的是地址, 所以EBP旧值实际上是上一栈帧的栈底地址.
movl %esp, %ebp: 由于上一栈帧仍未结束, 此时的EBP仍指向上一栈帧的栈底, 此时的ESP指向上一栈的栈顶, 即新压栈的EBP旧值所在内存空间. 此时将ESP的内容赋给EBP, 也就是将栈底指向此时的ESP的地址, 形成新的栈帧的底部. 由此, 栈底指针的指向离开上一栈帧, 指向当前栈帧的栈底, 完成新栈帧的开辟.
过程调用的参数传递
对于语句int x, y, z;, 在栈中, 存储位置的分配顺序是从右到左, 即zyx从高地址到低地址
- IA-32中,若参数类型是unsigned char、char或unsigned short、short,也都分配4个字节
- 故在被调用函数中,总使用R[ebp]+8、R[ebp]+12、R[ebp]+16作为有效地址来访问函数的入口参数


递归过程调用对程序性能的影响
在内存空间上, 每次递归调用都会增加一个新的栈帧, 在空间上有很大的开销. 在递归深度足够大时, 有可能发生栈溢出. 在时间上, 每次递归都需要执行准备和结束阶段的额外的指令, 对运行速度也会有明显的影响. 因此, 需要为了程序性能, 需要尽可能减少递归的使用.
不仅仅是递归的调用, 每个调用都会生成新的栈帧并执行准备和结束阶段的指令, 在程序中应该减少过程调用, 尤其是递归调用.


浙公网安备 33010602011771号