8.2 编程(www.cppentry.com)环境
x87 FPU有专用的指令和配套的寄存器。它的指令是x87浮点指令,配套的寄存器有:8个80位的数据寄存器、1个16位的控制寄存器、1个16位的状态寄存器、1个16位的标志寄存器、1个最后指令地址寄存器、1个最后操作数寄存器、1个操作码寄存器。其中,最后3个寄存器用于最后一次操作的信息记录,主要用于异常处理。如图8-1所示。
编程(www.cppentry.com)主要是围绕数据寄存器堆栈、状态寄存器和控制寄存器进行的,其他部分则多用于调试支持。
8.2.1 数据寄存器
x87 FPU有8个80位的数据寄存器(编号0~7),使用IEEE扩展双精度格式存储操作数,如图8-1所示。当内存中的数据载入数据寄存器时,如果数据格式不是扩展双精度格式,则在载入过程中进行格式转换。输出过程也是如此。
8个数据寄存器组成一个循环堆栈,栈顶记录保存于状态寄存器中,相当于堆栈指针。每次压栈(FLD指令载入数据),堆栈指针就减1,在0~7之间循环。代码并不直接使用这个指针操作这些寄存器,而是使用ST(0)~ST(7)表示。ST(0)指栈顶,即状态寄存器中栈顶指针指示的那个寄存器。如图8-2所示。
假设当前状态寄存器中的栈顶指针是N(即编号N的寄存器位于栈顶),则ST(i)对应的寄存器编号是:N+i mod 8
例如初始状态时,堆栈指针是0,FLD指令装载了一个数据以后,指针减小1是7,则ST(0)即是寄存器7,而ST(1)对应的寄存器编号是:7+1 mod 8 = 0
这些计算实际上是以CPU管理寄存器的方式看待这8个寄存器组成的堆栈,比较晦涩。还有一种方式不是从CPU角度理解,而是从指令角度(也就是编程(www.cppentry.com)角度)理解,直接将ST(0) ~ST(7)看成一个堆栈,ST(0)是栈顶,每次压栈,数据向上移动一个寄存器。例如载入一个数据,这个数据在ST(0)中,再次载入一个数据,则当前数据在ST(0)中,而上次载入的数据在ST(1)中。这种理解方式相对简易。
x87 FPU的数据寄存器是相对独立的,不受过程调用的影响。在线程、进程切换时,操作系统会保护这些寄存器,因此它们也不受线程、进程切换的影响。这意味着这些寄存器是个存放数据和传送参数的好地方,例如VC6就将ST(0)作为浮点返回值的存放场所。
绝大部分浮点指令都会影响数据寄存器,影响是多方面的:数据寄存器的内容、堆栈指针、标志寄存器等都会改变,例如FSIN指令就改写ST(0),自然标志寄存器也随之改变,其影响类似伪码:
- ST(0) = FSIN( ST(0) )
- TAG(0)= class( ST(0) )
而FINCSTP则仅改变指针,类似:
- TOP = TOP + 1
当然,大部分指令的影响要比这些复杂,例如FADDP指令,在使用时需要仔细阅读指令说明。
有一个细节需要注意,即通常使用FLD指令载入数据进行计算,使用FSTP等数据输出结果,但有时不需要输出数据寄存器(例如逻辑比较操作),而只是清空时,不能使用FINCSTP指令。虽然FINCSTP指令修改堆栈指针,但标志寄存器没有改变,因此堆栈并未清空。一个简单的方法是使用下列指令:
- FSTP ST(0)
数据寄存器、堆栈、标志寄存器等之间是联动的,在一般情形下,用户只需关注自己的数据处理逻辑,无需关注它们之间关系的细节,同时也应尽量避免干预(例如直接修改堆栈指针)。一个简单的指导原则就是将堆栈指针和标志寄存器当作只读的,仅用于调试。
还有一点需要特别注意,那就是MMX的数据寄存器与x87 FPU的数据寄存器虽然名字不同,实际上却是通过别名机制共用数据寄存器。这意味着,MMX指令和x87 FPU指令存在资源共享问题,不可同时使用。
【责任编辑:
董书 TEL:(010)68476606】