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:被调用过程)

img

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

示例

img

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作为有效地址来访问函数的入口参数

img

img

递归过程调用对程序性能的影响

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

不仅仅是递归的调用, 每个调用都会生成新的栈帧并执行准备和结束阶段的指令, 在程序中应该减少过程调用, 尤其是递归调用.

img

posted @ 2023-05-23 16:01  CINKUK  阅读(45)  评论(0)    收藏  举报