从x86函数(x87的flds / fstps)返回时,信号NaN被损坏,

我有奇怪的行为与x86(32位)的Linux GCC。 我使用gcc的builtin __builtin_nansf("")生成信号NaN,它生成0x7fa00000。 从函数返回这个值作为float后,它被修改为0x7fe00000。 有一个简短的例子:

 #include <stdio.h> float f = __builtin_nansf(""); float y; float func (void) { return f; } int main (void) { printf("%x\n", *((int*)&f)); y = func(); printf("%x\n", *((int*)&y)); } 

gcc-4.6.2 program.c编译的gcc-4.6.2 program.c ,其输出:

 7fa00000 7fe00000 

GDB:

 (gdb) p/xf $2 = 0x7fa00000 ... (gdb) si 0x08048412 in func () 1: x/i $pc 0x8048412 <func+14>: flds -0x4(%ebp) (gdb) x/x $ebp-4 0xbfffeb34: 0x7fa00000 (gdb) si (gdb) info all-regis st0 nan(0xe000000000000000) (raw 0x7fffe000000000000000) ... //after return from func (gdb) si 0x0804843d in main () 1: x/i $pc 0x804843d <main+38>: fstps 0x804a024 (gdb) si (gdb) x/x 0x804a024 0x804a024 <y>: 0x7fe00000 

为什么我的信号NaN被修改? 我怎样才能防止这种修改?

我不确定你可以防止这个。 在x87上加载sNaN通常会引发无效异常,然后通过设置(23位)尾数的msb将值转换为qNaN。 也就是说,与0x00400000进行或运算。

从英特尔 ®64 和IA-32架构软件开发人员手册中 ,第1卷,第4.8.3.4节介绍了sNan / qNan处理。 第8章介绍了X87 FPU编程。 卷3,22.18也描述了NaN如何由X87 FPU处理。

我没有看到X87控制字中的任何位都会产生您希望进行sNaN传播的行为。

在谷歌搜索“gcc 7fa00000”之后,我在GCC的bugzilla http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57484上发现了一个错误57484,并提供了一些有用的注释&#x3002;

UrošBizjak(GCC的i386 cpu端口维护者)在评论11,12,14和最后一篇中说,x86 ABI和x86-32 ABI不是完全支持x87上的IEEE754标准,而且“ 不幸的是,问题是不可修复的 ”:

就NaN而言,ABI对底层x87硬件来说只是错误的。

这个问题不幸是不可修复的。 x87和x86-32 ABI只是没有设计来处理IEEE 754标准的所有细节。

根据Uroš的说法,在x86 gcc目标上使用传统x87时,从内存到x87 FP寄存器(堆栈)的float和double加载被认为是将信号NAN(sNAN)改变为安静NAN(qNAN)的格式转换。 -msse2 -mfpmath=sse选项集有助于在SSE2中进行所有数学评估,但函数仍然通过x87堆栈返回FP值:

 $ gcc-4.6.3 -msse2 -mfpmath=sse test.c -o sse2math.out $ objdump -d sse2math.out ./c.out: file format elf32-i386 ... 08048404 <func>: 8048404: 55 push %ebp 8048405: 89 e5 mov %esp,%ebp 8048407: 83 ec 04 sub $0x4,%esp 804840a: a1 14 a0 04 08 mov 0x804a014,%eax 804840f: 89 45 fc mov %eax,-0x4(%ebp) 8048412: f3 0f 10 45 fc movss -0x4(%ebp),%xmm0 8048417: f3 0f 11 45 fc movss %xmm0,-0x4(%ebp) 804841c: d9 45 fc flds -0x4(%ebp) 804841f: c9 leave 8048420: c3 ret 

在添加了一个选项-mno-fp-ret-in-387 (全集是-msse2 -mfpmath=sse -mno-fp-ret-in-387 )之后,x87 fp寄存器不再用于传递float返回:

 08048404 <func>: 8048404: 55 push %ebp 8048405: 89 e5 mov %esp,%ebp 8048407: a1 14 a0 04 08 mov 0x804a014,%eax 804840c: 5d pop %ebp 804840d: c3 ret 

-mno-fp-ret-in-387选项会改变ABI,并可能打破许多库。