x87 FPU
:本文默认使用x87 FPU指令
注:x87 FPU特指与x86处理器配套的浮点协处理器架构
–浮点寄存器采用栈结构
- 深度为8,宽度为80位,即8个80位寄存器
- 名称为 ST(0) ~ ST(7),栈顶为ST(0),编号分别为 0~7
–所有浮点运算都按80位扩展精度进行
–浮点数在浮点寄存器和内存之间传送
- float、double、long double型变量在内存分别用IEEE 754单精度、
双精度和扩展精度表示,分别占32位(4B)、64位(8B)和96位(12B,其中高16位无意义)- float、double、long double类型变量在浮点寄存器中都用80位扩展精度表示
- 从浮点寄存器到内存:80位扩展精度格式转换为32位或64位
- 从内存到浮点寄存器:32位或64位格式转换为80位扩展精度格式
浮点处理指令
•数据传送类
(1) 装入(转换为80位扩展精度)
FLD:将数据从存储单元装入浮点寄存器栈顶 ST(0)
FILD:将数据从int型转换为浮点格式后,装入浮点寄存器栈顶
(2) 存储(转换为IEEE 754单精度或双精度)
FSTx:x为s/l时,将栈顶ST(0)转换为单/双精度格式,然后存入存储单元
FSTPx:弹出栈顶元素,并完成与FSTx相同的功能
FISTx:将栈顶数据从int型转换为浮点格式后,存入存储单元
FISTP:弹出栈顶元素,并完成与FISTx相同的功能
带P结尾指令表示操作数会出栈,也即ST(1)将变成ST(0)
(3) 交换
FXCH:交换栈顶和次栈顶两元素
(4) 常数装载到栈顶
FLD1:装入常数1.0
FLDZ:装入常数0.0
FLDPI:装入常数pi (=3.1415926...)
FLDL2E :装入常数log(2)e
FLDL2T:装入常数log(2)10
FLDLG2 :装入常数log(10)2
FLDLN2:装入常数Log(e)2
•算术运算类
(1)加法
FADD/FADDP:相加/相加后弹出栈
FIADD:按int型转换后相加
(2)减法
FSUB/FSUBP:相减/相减后弹出栈
FSUBR/FSUBRP:调换次序相减/相减后弹出栈
FISUB:按int型转换后相减
FISUBR:按int型转换并调换次序相减
若指令未带操作数,则默认操作数为ST(0)、ST(1)
带R后缀指令是指操作数顺序变反,例如:fsub执行的是x-y,fsubr执行的就是y-x
(3) 乘法
FMUL/FMULP: 相乘/相乘后弹出栈
FIMUL:按int型转换后相乘
(4) 除法
FDIV/FDIVP : 相除/相除后弹出栈
FIDIV:按int型转换后相除
FDIVR/FDIVRP:调换次序相除/相减后弹
问题一
(右边多一条赋值语句)
问题:使用老版本gcc -O2 编译时,程序一输出0,程序二输出1,是什么原因造成的?
-
先看函数的汇编
fld1:即将常数1.0压入栈顶ST(0)
fidivl:将指定的操作数epb寄存器内容+8地址中的01序列(即int x 此时为10),中的int型转换为double型,再将ST(0) (1.0)除以该数,并将结果存入ST(0)中 -
f(10)
f(10)=1.0(80位扩展精度)/10(转换为double)=0.1
0.1=0.00011[0011]B无限循环小数无法精确表示 -
程序一
a=f(10);
b=f(10);
i=a==b;
a=f(10);
把10作为参数调用函数,计算的结果赋给a,把f(10)存到0xfffffff8开始的64位单元中(即a的地址)
即从80位->64位
b=f(10);
同样把10作为参数调用函数,此时fldl把64位的a重入栈顶,即64位->80位,此时b在ST(1),
然后fucompp比较ST(0)和ST(1),b仍为80位
所以此时a!=b -
程序二
a=f(10);
b=f(10);
c=f(10;
i=a==b;
可以发现,a,b都先存入内存,80位->64位,c一直在浮点寄存器中,然后比较前a,b都入栈,即c到ST(2),a到ST(1),b到ST(0),ab同时从64位->80位所以比较时相等。
问题二
不都是强制转换吗?怎么会不一样?
关键差别为fldl和fildl
相信大家看了汇编代码后已经有了答案
参考:南大计算机系统基础(一)