我正在寻找GCC优化器错误的解决方法。这个bug在v4.5中,仍然存在于v5.3.0中,唉。这是问题C代码片段(类似printf的函数的一部分):
d *= factor;
if ((d > 0) && (d < 1)) /* 9.9e-1 instead of 0.99e0 */
{
exp--;
d *= 10;
}
else if (!sci && (d >= 10)) /* 1e0 instead of 10e-1 */
{
exp++;
d /= 10;
}
使用-O1或-O2时,此代码不会产生正确的结果。但是,如果我在“d * = factor”之后和“if”之前插入一个函数调用,如srand()或类似函数,那么代码编译正确并产生预期的结果。
我已经尝试插入其他东西来代替函数调用,试图将编译器推送到不同的状态而没有错误,但到目前为止只有函数调用似乎有效。
这引出了一个问题:对解决方法有任何更好的建议吗?
我无法为此生成一个小测试用例,或者我会将其报告为GCC错误。如果我从较大的函数中提取上面的代码段,它可以正常工作;它只会在长期复杂的整体功能中失败。
这是另一个与第一个形式非常相似的片段,它也有同样的问题。如果我在赋值和if之间插入函数调用,则问题就会消失。
gl = gc->mgr * pat_round(l / gc->mgr);
dl = l - gl;
if (((dl * gc->last_dl) < 0) &&
((gl == gc->last_gl) || ((gl * gc->last_gl) < 0)))
{
...
}
这两种情况都是双倍的比较。
这是第一个片段的黑客版本的汇编程序,其中插入了一个srand(0)调用以使其工作(我标记了用'*'更改的指令):
.L461:
fldl (%esp)
fmull 24(%esp)
fstpl (%esp)
subl $12, %esp
.cfi_def_cfa_offset 4252
pushl $0
.cfi_def_cfa_offset 4256
call srand
addl $16, %esp
.cfi_def_cfa_offset 4240
fldz
fldl (%esp)
fucomi %st(1), %st
fstp %st(1)
jbe .L559
fld1
fucomip %st(1), %st
jbe .L560
decl %ebp
fmuls .LC2
fstpl (%esp)
jmp .L457
并且删除了srand(0)也是同样的事情 - 这是非工作版本:
.L461:
fldl (%esp)
fmull 24(%esp)
fstl (%esp)
fldz
fxch %st(1)
fucomi %st(1), %st
fstp %st(1)
jbe .L559
fld1
fucomip %st(1), %st
jbe .L560
decl %ebp
fmuls .LC2
fstpl (%esp)
jmp .L457
答案 0 :(得分:4)
一些细节在这里会有所帮助,例如对意外行为的简要描述。由于缺乏这一点,我们不得不依靠心灵感应和水晶球,这是众所周知的不可靠。
根据我的ouija董事会,您的问题是,在该片段终止时,您希望d
处于半封闭范围[1,10]。但事实证明d
实际上是10.当您插入函数调用时,d
会神秘地更改为正确的值。
这可能发生,并且它不是一个优化错误。尽管double
具有固定的精度,并且在整个计算过程中名义上使用,但允许编译器以更高的精度执行中间计算,这可能导致变量在其各个点处看起来更精确。寿命。
现在,让我们通过心灵感应确定产品d * factor
是否略小于1,如果精确计算的话。实际上它非常接近1,如果它被舍入到53位精度,它将会舍入到1.0。但它恰好在那一点上有64位精度,所以它稍微少一些。现在我们乘以10,(因为它小于1,根据测试)并将结果舍入为53位,因为我们不再将值保存在寄存器中。圆形值将变为10,违背了预期(除了精神世界的期望,他们一直都知道它。)
插入函数调用将强制编译器将值保存在浮点寄存器中,因此在与1进行比较之前将其校正为53位,因此将比较相等,而不是更少。
当然,上述所有内容只是一种幻想,因为它在报告的证据中没有任何依据。如果事实证明与现实有任何相似之处,那将只是其中一个难以理解的巧合。
在该假设情况下,强制编译器使用SSE进行浮点运算将避免过多的精度计算,因为SSE寄存器仅为64位。或者,您可以告诉GCC更加努力地避免使用80位中间体。请参阅-mfpmath=sse
选项,-ffloat-store
和-fexcess-precision=standard
(SSE通常最好。)