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

时间:2011-11-24 02:27:27

标签: c gcc compiler-errors floating-point

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

 j=         0    9007199616606190.000000 = x
 k=         0    9007199616606190.000000 = [x]
 r=  31443101                   0.000000 = m*(x-[x]) 

但是当注释掉的行( //if (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;
 }

注意,测试系统=带有Linux 2.6.35.14-96.fc14.i686的AMD Athlon 64 5200+系统(,启动以在64位硬件上运行32位操作系统) gcc(GCC)4.5.1 20100924(Red Hat 4.5.1-4)

更新 - 几个小时前,我发表评论说,使用和不使用if语句生成的代码仅在堆栈偏移和一些跳过的代码中有所不同。我现在发现评论并不完全正确; 即。对于非优化代码是正确的,但对于我执行的-O3代码则不然。

优化开关对问题的影响:

  • -O0:两个程序版本都运行正常
  • -O2或-O3:带注释的版本上面有错误,其中j=20000k=17285
  • -O1:包含评论的版本j=20000(错误)和k=0(确定)

无论如何,看看-O3 -S代码清单,这两种情况大多不同于跳过的if代码和堆栈偏移,直到call floor之前的行,此时if-if代码具有比无if代码多一个fstpl

    ...  ;; 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
    ...

我还没弄清楚差异的原因。

4 个答案:

答案 0 :(得分:3)

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

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

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


顺便说一句,这是您环境中的严重失败。当m为10000时,这意味着x - floor(x)必须等于2.我不能为我的生活想到任何实际数字的情况: - )

答案 1 :(得分:2)

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

您可以尝试使用-mfpmath=sse -march=native将SSE单元用于浮点计算,这不会显示出过高的精度;或者您可以尝试使用-ffloat-store开关。

你的减法x - floor(x)表现出灾难性的取消 - 这是问题的根本原因要避免的事情;)。

答案 2 :(得分:2)

我认为这条线可能产生影响有两个原因:

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

答案 3 :(得分:0)

编辑:

当我使用-O0,-O1,-O2和-O3在我的计算机上编译代码时,我也没有看到任何区别。

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

我也尝试过3.0版本的clang(llvm),有没有相同的结果。

我同意编译器可以预先计算所有内容,如果是行,你肯定会在程序集输出中看到它。

浮点和C可能很讨厌,很多东西要知道才能让它真正起作用。强制int进行双重转换对于准确性是有好处的(编译器中的c库,即使已知fpu已经存在问题,并且它使用的编译器C库以及编译到程序中或由程序使用的C库可以/将不同的),但是从/浮动的int是FPU倾向于有它们的bug(我想我看到了TestFloat或类似的地方提到过)。可能会尝试在您的系统上运行TestFloat以查看您的FPU是否良好。在着名的奔腾浮点错误和PentiumIV之间,以及大多数处理器都有浮点错误,我所拥有的奔腾III是稳固的,但我所拥有的Pentium IV会失败。我很少使用浮点,所以不用费心去测试我的系统。

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