c语言浮点数异常,汇编语言浮点数异常与常用指令集

每个程序都可能出错,而 FPU 就需要处理这些结果。因而,它要识别并检测 6 种类型的异常条件:

无效操作(#I)

除零(#Z)

非规格化操作数(#D)

数字上溢(#O)

数字下溢(#U)

模糊精度(#P)

前三个(#I、#Z 和 #D)在全部运算操作发生前进行检测,后三个(#O、#U 和 #P)则在操作发生后检测。

每种异常都有对应的标志位和屏蔽位。当检测到浮点异常时,处理器将与之匹配的标志位置 1。每个被处理器标记的异常都有两种可能的操作:

如果相应的屏蔽位置 1,那么处理器自动处理异常并继续执行程序。

如果相应的屏蔽位清 0,那么处理器将调用软件异常处理程序。

大多数程序普遍都可以接受处理器的屏蔽(自动)响应。如果应用程序需要特殊响应,那么可以使用自定义异常处理程序。一条指令能触发多个异常,因此处理器要持续保存自上一次异常清零后所发生的全部异常。完成一系列计算后,可以检测是否发生了异常。

浮点数指令集

FPU 指令集有些复杂,因此这里只对其功能进行概述,并用具体例子给出编译器通常会生成的代码。此外,大家还将看到如何通过改变舍入模式来控制 FPU。指令集包括如下基本指令类型:

数据传送

基本算术运算

比较

超越函数

常数加载(仅对专门预定义的常数)

x87 FPU 控制

x87 FPU 和 SIMD 状态管理

浮点指令名用字母 F 开头,以区别于 CPU 指令。指令助记符的第二个字母(通常为 B 或 I)指明如何解释内存操作数:B 表示 BCD 操作数,I 表示二进制整数操作数。

如果这两个字母都没有使用,则内存操作数将被认为是实数。比如,FBLD 操作对象为 BCD 数值, FILD 操作对象为整数,而 FLD 操作对象为实数。

操作数

浮点指令可以包含零操作数、单操作数和双操作数。如果是双操作数,那么其中一个必然为浮点寄存器。指令中没有立即操作数,但是某些预定义常数(如 0.0,π 和 log210)可以加载到堆栈。

通用寄存器 EAX、EBX、ECX 和 EDX 不能作为操作数。(唯一的例外是 FSTSW,它将 FPU 状态字保存在 AX 中。)不允许内存-内存操作。

整数操作数从内存(不是从 CPU 寄存器)加载到 FPU,并自动转换为浮点格式。同样,将浮点数保存到整数内存操作数时,该数值也会被自动截断或舍入为整数。

初始化(FINIT)

FINIT 指令对 FPU 进行初始化。将 FPU 控制字设置为 037Fh,即屏蔽(隐藏)了所有浮点异常;舍入模式设置为最近偶数,计算精度设置为 64 位。建议在程序开始时调用 FINIT, 这样就可以了解处理器的起始状态。

浮点数据类型

现在快速回顾一下 MASM 支持的浮点数据类型(QWORD、TBYTE、REAL4、REAL8 和 REAL10),如下表所示。

类型

用法

QWORD

64 位整数

TBYTE

80 位(10 字节)整数

REAL4

32 位(4 字节)IEEE 短实数

REAL8

64 位(8 字节)IEEE 长实数

REAL10

80 位(10 字节)IEEE 扩展实数

在定义 FPU 指令 的内存操作数时,将会使用到这些类型。例如,加载一个浮点变量到 FPU 堆栈,这个变量可以定义为 REAL4,REAL8 或 REAL10:

.data

bigVal REAL10 1.212342342234234243E+864

.code

fld bigVal              ;加载变量到堆栈

加载浮点数值(FLD)

FLD(加载浮点数值)指令将浮点操作数复制到 FPU 堆栈栈顶(称为 ST(0))。操作数可以是 32 位、64 位、80 位的内存操作数(REAL4、REAL8、REAL10)或另一个 FPU 寄存器:

FLD m32fp

FLD m64fp

FLD m80fp

FLD ST(i)

内存操作数类型 FLD 支持的内存操作数类型与 MOV 指令一样。示例如下:

.data

array REAL8 10 DUP (?)

.code

fid array                                                  ;直接寻址

fid [array+16 ]                                        ;直接偏移

fid REAL8 PTR[esi]                                  ;间接寻址

fid array[esi]                                           ;变址寻址

fid array[esi*8]                                        ;带比例因子的变址

fid array[esi*TYPE array]                         ;带比例因子的变址

fid REAL8 PTR[ebx+esi]                          ;基址-变址

fid array[ebx+esi]                                   ;基址-变址-偏移量

fid array[ebx+esi*TYPE array]                 ;带比例因子的基址-变址-偏移量

【示例】下面的例子加载两个直接操作数到 FPU 堆栈:

.data

dblOne REAL8 234.56

dblTwo REAL8 10.1

.code

fid dblOne            ; ST(0) = dblOne

fid dblTwo            ; ST(0) = dblTwo, ST(1) = dblOne

每条指令执行后的堆栈情况如下图所示:

7e28a5c496d5fbae4354d1b568a293eb.gif

执行第二条 FLD 时,TOP 减 1,这使得之前标记为 ST(0) 的堆栈元素变为了 ST(1)。

FILD

FILD(加载整数)指令将 16 位、32 位或 64 位有符号整数源操作数转换为双精度浮点数,并加载到 ST(0)。源操作数符号保留。FILD 支持的内存操作数类型与 MOV 指令一致(间接、变址、基址-变址等)。

加载常数

下面的指令将特定常数加载到堆栈。这些指令没有操作数:

FLD1 指令将 1.0 压入寄存器堆栈。

FLDL2T 指令将 log210 压入寄存器堆栈。

FLDL2E 指令将 log2e 压入寄存器堆栈。

FLDPI 指令将  π  压入寄存器堆栈。

FLDLG2 指令将 log102 压入寄存器堆栈。

FLDLN2 指令将 loge2压入寄存器堆栈。

FLDZ(加载零)指令将 0.0 压入 FPU 堆栈。

保存浮点数值(FST, FSTP)

FST(保存浮点数值)指令将浮点操作数从 FPU 栈顶复制到内存。FST 支持的内存操作数类型与 FLD 一致。操作数可以为 32 位、64 位、80 位内存操作数(REAL4、REAL8、 REAL10)或另一个 FPU 寄存器:

FST m32fp FST m80fp

FST m64fp FST ST(i)

FST 不是弹出堆栈。下面的指令将 ST(0) 保存到内存。假设 ST(0) 等于 10.1,ST(1) 等于 234.56:

fst dblThree   ; 10.1

fst dblFour     ; 10.1

直观地说,代码段期望 dblFour 等于 234.56。但是第一条 FST 指令把 10.1 留在 ST(0) 中。如果代码段的意图是把 ST(1) 复制到 dblFour,那么就要用 FSTP 指令。

FSTP

FSTP(保存浮点值并将其出栈)指令将 ST(0) 的值复制到内存并将 ST(0) 弹出堆栈。假设执行下述指令前 ST(0) 等于 10.1,ST(1) 等于 234.56:

fstp dblThree ; 10.1

fstp dblFour ; 234.56

指令执行后,这两个数值会从堆栈中逻辑移除。从物理上看,每次执行 FSTP,TOP 指针都会减 1,修改 ST(0) 的位置。

FIST(保存整数)指令将 ST(0) 的值转换为有符号整数,并把结果保存到目标操作数。保存的值可以为字或双字。FIST 支持的内存操作数类型与 FST 一致。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值