C 未注释掉未使用语句时的浮点异常?

C 未注释掉未使用语句时的浮点异常?,c,gcc,compiler-errors,floating-point,C,Gcc,Compiler Errors,Floating Point,当运行如下所示的程序时,它产生ok输出: j= 0 9007199616606190.000000 = x k= 0 9007199616606190.000000 = [x] r= 31443101 0.000000 = m*(x-[x]) 但是当注释掉的行(即,//如果(argc>1)r=atol(argv[1]);)未注释时,它会生成: j= 20000 90071996166061

当运行如下所示的程序时,它产生ok输出:

 j=         0    9007199616606190.000000 = x
 k=         0    9007199616606190.000000 = [x]
 r=  31443101                   0.000000 = m*(x-[x]) 
但是当注释掉的行(即,
//如果(argc>1)r=atol(argv[1]);
)未注释时,它会生成:

 j=     20000    9007199616606190.000000 = x
 k=     17285    9007199616606190.000000 = [x]
 r=  31443101                   0.000000 = m*(x-[x]) 
即使该行应该没有效果,因为
argc>1
为false。有人对这个问题有合理的解释吗?它在任何其他系统上都是可复制的吗

 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>
 int main(int argc, char *argv[]) {
   int  j, k, m=10000;
   double r=31443101, jroot=sqrt(83), x;
   //if (argc>1) r = atol(argv[1]);
   x = r * r * jroot;
   j = m*(x-floor(x));
   k = floor(m*(x-floor(x)));
   printf ("j= %9d   %24.6f = x\n", j, x);
   printf ("k= %9d   %24.6f = [x]\n", k, floor(x));
   printf ("r= %9.0f   %24.6f = m*(x-[x]) \n", r, m*(x-floor(x))); 
   return 0;
 }

我还没有找出差异的原因。

在我的系统上没有重复,Win7运行CygWin和GCC4.3.4。无论是否使用
if
语句,
j
的值都设置为零,而不是20K

我唯一的建议是使用
gcc-S
查看汇编程序的输出。这应该能告诉你出了什么问题

具体来说,将汇编程序输出生成两个单独的文件,一个用于工作变量,一个用于非工作变量,然后vgrep(并排观察它们)以尝试确定差异



顺便说一下,这在您的环境中是一个严重的失败。当
m
为10000时,这意味着
x-楼层(x)
必须等于2。在我的一生中,我想不出任何实数,在这种情况下:-)

取消注释该行可能会影响结果的原因是,如果没有该行,编译器可以看到
r
jroot
在初始化后无法更改,因此,它可以在编译时而不是运行时计算
x
。当行未注释时,
r
可能会更改,因此必须将
x
的计算推迟到运行时,这可能导致以不同的精度进行计算(特别是在使用387浮点数学的情况下)

您可以尝试使用
-mfpmath=sse-march=native
来使用sse单位进行浮点计算,它不会显示过多的精度;或者您可以尝试使用
-ffloat store
开关


您的减法
x-floor(x)
显示出灾难性的取消-这是问题的根本原因,需要避免;)

我认为有两个原因可以解释为什么这条线会产生影响:

  • 如果没有这一行,所有这些变量的值都可以(而且,IMHO,很可能是)在编译时确定;使用该行,必须在运行时执行计算。但是很明显,编译器的预计算值应该与运行时计算的值相同,我倾向于认为这是观察到的不同行为的实际原因。(不过,它肯定会在汇编程序输出中显示出巨大的差异!)
  • 在许多机器上,浮点运算是使用中间值中比实际存储在双精度浮点数中更多的位来执行的。您的第二个版本通过创建两个不同的代码路径来设置
    x
    ,基本上将
    x
    限制为可以存储在双精度浮点数中的内容,而您的第一个版本允许
    x
    的初始计算值仍然可以作为中间值使用,并带有额外的位,计算后续值时。(无论这些值是在编译时还是在运行时计算的,都可能是这种情况。)
编辑:

当我在我的计算机上使用-O0、-O1、-O2和-O3编译你的代码时,我也看不出有什么不同

AMD Phenom四路64位。 gcc(Ubuntu 4.4.3-4ubuntu5)4.4.3

我还尝试了3.0版中的clang(llvm),无论结果是否相同

我同意编译器可以在没有if行的情况下预计算所有内容,但您肯定会在汇编输出中看到这一点

浮点和C可能很糟糕,需要知道很多东西才能让它真正工作。强制int-to-double转换有助于提高精度(编译器中的c库,即使fpu是好的,也已经知道存在问题,并且它使用的编译器c库和编译到或由您的程序使用的c库可能会有所不同),但是int-to/from-float是fpu容易出现错误的地方(我想我在TestFloat或类似的地方看到了这一点)。可能会尝试在您的系统上运行TestFloat,看看您的FPU是否良好。在著名的奔腾浮点错误和奔腾IV之间以及之后的几天中,大多数处理器都有浮点错误,我的奔腾III是可靠的,但我的奔腾IV会失败。我很少再使用浮点,所以不必费心测试我的系统


使用优化确实会根据您的编辑更改您的结果,因此这很可能是gcc问题,或者是您的代码和gcc的组合(而不是硬件fpu问题)。然后在同一台计算机上尝试不同版本的gcc。例如,4.4.x而不是4.5.x。

该代码在我的Linux和OS x系统上产生相同的输出——无论该行是否被注释掉。对我来说不可复制;
j=0
k=0
在有注释行和没有注释行的情况下运行。Ubuntu 11.04,
gcc版本4.5.2(Ubuntu/Linaro 4.5.2-8ubuntu4)
,英特尔核心i7 920(系列6,型号26,步进4)。(64位操作系统和用户区。)我在我的Mac OS机器上都得到了前一个结果。在4.0到4.6的几个不同版本的gcc中。看起来这可能是您的情况所特有的。我无法用完全相同版本的
gcc
重现这个问题,尽管gcc本身编译为x86-64二进制(当然,使用
-m32
编译32位目标)。@jwpat7,您应该显示这两种情况下的所有汇编代码。我区分了文件,区别在于:(1)使用
if
in时,多了8个堆栈访问偏移量,以及(2)atol()的10行代码呼叫,通过一个
jle.L2
跳转。我要在我的ot上试用该代码
    ...  ;; code without comment:
fmul    %st, %st(1)
fxch    %st(1)
fstpl   (%esp)
fxch    %st(1)
fstpl   48(%esp)
fstpl   32(%esp)
call    floor
movl    $.LC2, (%esp)
fnstcw  86(%esp)
movzwl  86(%esp), %eax
    ...
    ...  ;; versus code with comment:
fmul    %st, %st(1)
fxch    %st(1)
fstpl   (%esp)
fxch    %st(1)
fstpl   48(%esp)
fstpl   32(%esp)
fstpl   64(%esp)
call    floor
movl    $.LC3, (%esp)
fnstcw  102(%esp)
movzwl  102(%esp), %eax
    ...