设为首页 加入收藏

TOP

8.2.2 状态寄存器
2013-10-07 00:51:14 来源: 作者: 【 】 浏览:57
Tags:8.2.2 状态 寄存器

8.2.2  状态寄存器

x87 FPU有一个16位寄存器显示当前的状态:空闲标志(1位)、条件判断标志(4位)、数据寄存器堆栈指针(3位)、异常标志(6位)、全局错误标志(1位)、堆栈错误标志(1位)。如图8-3所示:

 

一般在一个操作完成以后,通过检测状态寄存器控制流程。在一般编程(www.cppentry.com)中,异常标志和条件标志是最重要的。其他部分仅在出现错误时才需要关注,详情请参阅文献[2]。

(1)指令

状态寄存器是只读的,因此与它有关的指令只有一条(但有同步和不同步两个版本,参见8.3节),即FSTSW。这条指令将状态寄存器中的内容传送至AX寄存器,位次序不变。由于状态寄存器共16位,因此正好填满AX。通过检测AX的对应位,即可得到状态控制器的相关信息,例如前面检测条件位C0的代码:

  1. FSTSW                     ;传送至AX  
  2. AND    EAX,100h           ;测试C0=1  
  3. JNZ    MYNEXT2           ;ST0 < ST1 

(2)堆栈指针

位11至位13是堆栈指针(共3位),其值在0和7之间循环。

与常规x86指令使用寄存器模式不同,浮点指令使用堆栈模式(更确切地说,是寄存器模式和堆栈模式的混合),即一般的浮点指令从栈顶取操作数,结果也存储于栈顶,例如FADDP指令就是如此,它的操作数来自ST(0)和ST(1),结果存储于ST(0)。

一般情形下,用户只需保证堆栈不出现错误(例如溢出),无需特别在意它的指针。但在特殊情形下,移动堆栈指针可能有利于提高效率。例如正在进行一个复杂的计算,中间结果占据了多个数据寄存器,如图8-4所示。

  

假设当前的堆栈指针位于data5处,而需要计算sin(data2),那么如何实现呢?常规方法(不直接操作堆栈指针)代码如下:

  1. FSTP  data5  
  2. FSTP  data4  
  3. FSTP  data3  
  4. FSIN  data2  
  5. FLD  data3  
  6. FLD  data4  
  7. FLD  data5 

如果直接操作堆栈指针(使用FINCSTP指令),那么代码如下:

  1. FINCSTP  
  2. FINCSTP  
  3. FINCSTP  
  4. FSIN  
  5. FDECSTP  
  6. FDECSTP  
  7. FDECSTP 

可以看出,在复杂计算中,常规方法的代价就是在数据寄存器和内存之间进行多次数据传送,降低了执行效率,而直接操作堆栈指针可以避免这些问题。当然,这个例子是编造的,实际代码可能没有这么极端,而且操作堆栈指针相当危险(清空堆栈时很容易造成堆栈溢出)。除非迫不得已,一般不建议这么做。

(3)异常位

第5章已经提及IEEE定义了5种浮点异常,每种异常均对应一个位,共5个位。除此而外,还有一个弱规范数异常,当操作数中出现弱规范数时,这个异常位就会被设置(但扩展双精度格式例外)。

在这些硬件基础上,可以建立两种异常处理模式:正常模式和安静模式。

正常的异常处理是硬件触发异常,被内核(操作系统代码)捕获,然后由内核传递至用户层,交由指定的异常代码处理。这种处理模式的特点是:当一切正常、没有异常触发时,异常处理代码不会被激活,如同不存在一样,因此任务代码执行效率较高;但是一旦触发了异常,内核代码和用户代码均被激活,处理代码的执行效率较低,系统受到较大干扰。

安静模式就是屏蔽一切异常,但在任务代码执行过程中,在一些关键处(例如结果出来时)进行异常检测。如果检测到异常就进行异常处理,否则继续执行任务代码。这种模式即使在异常出现时也不会激活内核代码,对系统干扰较小,异常处理代码的效率也高。但是,大量的检测代码即使在没有异常时也需要运行(否则不知道是否发生了异常),从而导致任务代码效率低下。

两种模式各有优缺点,选择的关键在于异常被触发的频率。如果频率较低,那么正常模式有优势;反之,安静模式有优势。幸运的是,在浮点运算中,这两种模式都可以根据情形选用。Windows标准的异常处理模式就是正常模式,常见的try-except块和try-catch块就是这种模式在C/C++(www.cppentry.com)中的对应物。与此同时,VC6的浮点数学库使用安静模式处理数学函数内部可能出现的异常,例如,如果想处理asin()可能出现的定义域错误(非法操作异常的一种情形),那么只需提供一个_matherr()即可。不过这种模式仅用于VC6的数学函数库,对普通的浮点代码无效(VC6浮点库没有提供异常检测函数)。

关于异常处理以及使用参见第12章。

(4)条件位

状态寄存器有4个条件位,虽然最常见的用处是给出逻辑比较的结果,但这只是它们在逻辑比较指令中的作用,在别的指令中,它们还有其他作用,例如在FXAM指令中它们给出操作数的类型、在FPREM指令中它们返回商的最低3个位。考虑到这些,更确切的名字应该是指示位。

按返回结果方式的不同,x87 FPU有两类逻辑比较指令:一类指令直接将结果设置到EFLAGS寄存器中,例如FCOM指令;一类则将结果设置在状态寄存器的条件位中,例如FCOMI指令。两者在使用上也有差异,例如比较两个数的大小,使用FCOMI指令是:

  1. FLD    QWORD PTR[ESI]  
  2. FLD    QWORD PTR[ESI+8]  
  3. FCOMI  ST(0), ST(1)       ;占用EFLAGS部分  
  4. JB      MYNEXT2 

需要将两个数都载入寄存器,但检测结果比较方便。而FCOM指令正好相反:

  1. FLD    QWORD PTR[ESI]  
  2. FCOM   QWORD PTR[ESI+8]  
  3. FSTSW                       ;占用AX  
  4. AND    EAX,100h             ;测试C0=1  
  5. JNZ     MYNEXT2         ;ST0 < ST1 

只需载入一个即可,但检测结果比较麻烦,而且会破环AX。

我喜欢使用FCOMI指令(此时无需关心C0,C1,C2,C3的意义),但在VC6浮点库中没有见到FCOMI指令。这里有一个重要的原因,那就是FCOMI指令是Pentium 6系列才引入IA-32体系的,先前的CPU不支持。因此,如果需要考虑兼容性,FCOM指令是唯一的选择。C0,C1,C2,C3的意义参见附录B指令说明部分。

另一个需要特别注意的细节是FCOMI指令的特性,它只设置EFLAGS寄存器的ZF、CF和PF,没有设置SF和OF,因此FCOMI指令的结果类似无符号整型的结果,这意味着紧跟的分支指令应该使用JA/JAE/JE/JBE/JB,而不要使用JG/JGE/JE/JLE/JL。否则,结果会出错。

【责任编辑:董书 TEL:(010)68476606】

回书目   上一节   下一节

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇8.2.3 控制寄存器 下一篇8.2.1 数据寄存器

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: