4

我对 x86(32 位)linux gcc 有奇怪的行为。我使用 gcc 的内置生成信号 NaN __builtin_nansf(""),生成 0x7fa00000。从函数返回这个值作为浮点数后,它被修改为 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,其输出:

7fa00000
7fe00000

数据库:

(gdb) p/x f   
$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 被修改了?如何防止这种修改?

4

2 回答 2

4

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

Intel® 64 and IA-32 Architectures Software Developer Manuals , Vol 1, 4.8.3.4 描述了sNan/qNan 处理。第 8 章讨论 X87 FPU 编程。第 3 卷 22.18 还描述了 X87 FPU 如何处理 NaN。

我在 X87 控制字中看不到任何位会产生您想要的 sNaN 传播行为。

于 2014-04-02T18:11:08.787 回答
3

在谷歌搜索“gcc 7fa00000”后,我在 GCC 的 bugzilla http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57484中找到了错误 57484,其中包含一些有用的评论。

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 寄存器(堆栈)的浮点和双精度负载被认为是格式转换,将信号 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 寄存器不再用于传递浮点返回:

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,并且可能会破坏许多库。

于 2014-04-02T17:45:39.587 回答