程序的机器级表示二(控制)
目录
实现有条件的行为:
- 测试数据值
- 根据测试的结果来改变测试流/数据流
1.条件码
- CF 最高位产生了进位,无符号操作数的溢出
- SF 符号标志,操作结果为负数
- ZF 零标志
- OF 溢出标志 (有符号数 signed)
lea/mov指令不设置条件码
- 逻辑操作,例如 XOR,进位标志和溢出标志会设置成 0
- 移位操作,进位标志将设置为最后一个被移出的位,而溢出标志设置为0
- INC DEC 指令会设置溢出和零标志,但是不会改变进位标志
比较和测试指令:
CMP 类似 SUB 指令的行为,只设置条件码而不更新目标寄存器。
TEST 类似AND,只设置条件码而不更新目标寄存器。
访问条件码:
用法:①条件码组合,设置某个字节 ②条件跳转 ③条件传送
set寄存器单字节,要清零寄存器高位字节。
2.跳转指令
跳转的目的地用标号之名,产生目标文件时,汇编器确定带标号指令的地址,将跳转目标的编码为指令一部分
- 直接跳转: jmp .L1
- 间接跳转: ①jmp *(%eax) 用寄存器里面的值作为读地址,从存储器读出跳转目标 ②jmp *%eax用寄存器eax里的值作为跳转目标
- 除jmp外,其他跳转指令根据条件码的组合决定是否跳转
编码方式:最常用PC相关,编码值=addr(目标) - PC(eip)
eg:
第1行 jle 执行时,eip=0xa,目标dest2=0x17 编码=dest2-eip=0xd
第7行 jg 执行时, eip=0x17,目标dest1=0xa 编码=dest1-eip=-13=0xf3
3.条件传送指令
分支预测惩罚:
处理器通过使用流水线 (pipelining) 来获得高性能,通过重叠连续指令的步骤来获得高性能。
当机器遇到条件跳转(也称为“分支")时,它常常还不能够确定是否会 进行跳转。处理器采用非常精密的分支预测逻辑试图猜测每条跳转指令是否会执行。只要它的 猜测还比较可靠(现代微处理器设计试图达到 90% 以上的成功率),指令流水线中就会充满着指令。另一方面,错误预测一个跳转要求处理器丢掉它为该跳转指令后所有指令已经做了的工作, 然后再开始用从正确位置处起始的指令去填充流水线。正如我们会看到的,这样一个错误预测会招致很严重的惩罚。大约 20-40 个时钟周期的浪费,导致程序性能的严重下降。
条件传送:
与条件跳转不同,处理器可以执行条件传送,而无需预测测试的结果。处理器只是读源值(可能是从存储器中),检查条件码,然后要么更新目的寄存器,要么保持不变。
不是所有的条件表达式都可以用条件传送来编译。可能出现非法引用,改变全局变量等等:
只有当两个表达式都很容易计算时,例如表达式分别都只是一条加法指令,它才使用条件传送。
4.switch语句
- 提高了代码的可读性
- 通过使用跳转表 (jump table) 这种数据结构使得实现更加高效
- 使用跳转表的优点是执行开关语句的时间与开关情况的数量无关
当开关情况数量比较多(例如 4个以上),并且值的范围跨度比较小时,就会使用跳转表。
数组 jt 包含7个表项,每个都是一个代码块的地址。
使用跳转表是一种非常有效的实现多重分支的方法。当switch 语句有上百种情况的时候,也可以只用一次跳转表访问去处理。