这章主要讲了条件语句,循环语句,switch语句在计算机中是如何实现的
条件码
除了整数寄存器,计算机还有保存最近算术和逻辑操作的条件码寄存器
条件码寄存器保存的是单个位的数据
举个例子if(a>2){}
,计算机会让a-2,这时候计算机会根据计算的结果自动变动条件码寄存器的值。最后根据一个条件码或多个条件码判断是否跳转,具体见后文。
这些条件码在计算机运算的时候根据最近的计算结果会自动的更改状态
例如某次运算结果为0,那么ZF会变成1,如果结果不为0,那么ZF会变成0
例如上面的操作都会改变条件码
有两类指令只为了改变条件码,而不改变其他寄存器,例如在比较两个数的大小的时候会用到这种指令
用于改变条件码的指令
下图中表示的是在使用该指令的时候操作数实际被用于什么样的运算,状态码就是被这次运算改变的.
访问条件码
条件码通常不会直接被读取,常用的使用方法有三种
- 根据条件码的组合,使用指令设置某个字节为0或1.
- 可以调整到程序的某个其他部分
- 可以有条件的传送数据
第一种情况用的指令被称为SET指令
注意图中的后缀不代表它是用于某个数据类型的.不要认为setb是操作字节的
SET指令的目的操作数是8个整数寄存器之一,或是一个字节大小的存储器,它会把一个字节设置为0或1.但如果想获得一个32位(4字节)结果,需把前24位清零.
例子
C语言中的a<b
其汇编为
未溢出情况下
Created with Rapha?l 2.1.4开始a < b返回1a-b<0SF=1,OF=0,SF^OF=1结束返回0a-b>0SF=0,OF=0,SF^OF=0yesno
start=>start: 开始
end=>end: 结束
cond1=>condition: a < b
ret1=>operation: 返回1
ret0=>operation: 返回0
op2=>operation: a-b<0
op3=>operation: a-b>0
result0=>operation: SF=0,OF=0,SF^OF=0
result1=>operation: SF=1,OF=0,SF^OF=1
start->cond1(yes)->ret1
cond1(no)->ret0
ret1->op2->result1->end
ret0->op3->result0->end
溢出情况下
Created with Rapha?l 2.1.4开始a《b返回0a-b>0因为b是很小的负数SF=1,OF=1,SF^OF=0结束返回1a-b<0因为b是很大的正数SF=0,OF=1,SF^OF=1yesno
start=>start: 开始
end=>end: 结束
cond=>condition: a《b
ret1=>operation: 返回0
ret0=>operation: 返回1
op1_1=>operation: a-b>0
op0_1=>operation: a-b<0
op1_2=>operation: 因为b是很小的负数
op0_2=>operation: 因为b是很大的正数
result0=>operation: SF=1,OF=1,SF^OF=0
result1=>operation: SF=0,OF=1,SF^OF=1
start->cond(yes)->ret1->op1_1->op1_2->result0->end
cond(no)->ret0->op0_1->op0_2->result1->end
跳转指令
跳转指令分为
- 直接跳转,即跳转目标直接写在指令中比如图中的.L1
- 间接跳转,从寄存器或存储器中获得地址并跳转
jmp *%eax
//注意间接跳转需要有个*
图中所示的指令除了jmp其他都是条件跳转
其配合条件码来决定是否跳转
条件跳转只能是直接跳转
跳转指定跳转的目标位置(Lable)有多中的编码方式
最常用的是PC相关
PC,Program Counter,程序计数器,总是指向CPU下一条执行的指令位置
这种方式编码,jmp 的操作数写的是跳转目标指令的地址减去PC中保存的值
例子
条件分支
条件语句常混合使用有条件和无条件跳转
条件传送指令
//可能要先看循环
部分
故名思议该指令就是在一定条件下才执行数据转移
1995年PentiumPro才开始有条件传送指令,但是当时GCC为了向后兼容,默认不翻译出带条件传送指令的汇编代码
条件传送指令性能更好,但并不是绝对的
a,b图的作用是一样的,b图的代码跟接近汇编代码的样子
如果不使用带条件指令的汇编将可能会是这样
如果使用了
最后一个指令cmov
(Condition move),l
(less)
意思是如果x<y则把y-x设置为返回值
乱入的知识点
不明白这个知识写为什么要写在这
大概是说CPU使用流水线技术在取一台哦指令的时候,执行他前面一条指令的算术运算
,也就是说在前一条指令未执行完成后一条工作任务就要拿到手上.对于条件判断,在运算完成之前是不知道下面的任务的,于是都靠猜
.
原文写到处理器采用非常精密的 分支预测逻辑 试图猜测每条跳转指令是否会执行
,而该预测的成功率是90%.
如果预测错误,那么丢掉它该跳转指令后所有指令已经做了的工作,然后在开始从正确位置处起始的指令去填充流水线.
指令
条件传送指令的传送条件是依赖于条件寄存器
该指令汇编器会自动根据寄存器判断数据类型,于是指令末尾不需要加l,w等
例子
当未使用条件传送指令编译器生成的代码
使用了传送指令编译器生成的代码
易知,使用了传送指令的代码,他把条件运算符中的then-expr和else-expr都计算了.这种多余的计算在某些时候会产生问题比如//只给例子
所以条件传送指令并不总是改善代码效率
循环
汇编中主要根据do-while循环编译而成,其他形式的循环只是在do-while版本的循环上稍作修改
while 循环
while (test-expr)
body-statement
do-while版:
if(!test-expr)
goto done;
do
body-statement
while (test-expr);
done;
goto版:
t=test-expr;
if(!t)
goto done;
loop:
body-statement;
t=test-expr;
if(t)
goto loop;
done:
for循环
for(init-expr;test-expr;update-expr)
body-statement
do-while版
init-expr;
if(!test-expr)
goto done;
do {
body-statement
update-expr;
} while (test-expr)
done:
goto版
init-expr;
t = test-expr;
if(!t)
goto done;
loop:
body-staement
update-expr;
t = test-expr;
if(t)
goto loop;
done:
switch
当switch的条件比较多,且被switch的值范围跨度大的时候就会用跳转表
提高性能.
跳转表是保存了一堆代码段位置的数组
直接给书中的例子
右图中*jt[7]
数组中保存的值都是&&开头,运算符&代表创建以后只想数据值得指针,&&代表创建一个只想代码位置的指针
汇编版: