为什么生成的相同汇编代码不会导致相同的输出?

Why doesn't the same generated assembler code lead to the same output?

示例代码(t0.c):

#include <stdio.h>

float f(float a, float b, float c) __attribute__((noinline));
float f(float a, float b, float c)
{
    return a * c + b * c;
}

int main(void)
{
    void* p = V;
    printf("%a\n", f(4476.0f, 20439.0f, 4915.0f));
    return 0;
}

调用和执行(通过godbolt.org):

# icc 2021.1.2 on Linux on x86-64
$ icc t0.c -fp-model=fast -O3 -DV=f
0x1.d32322p+26
$ icc t0.c -fp-model=fast -O3 -DV=0
0x1.d32324p+26

生成的汇编代码相同:https://godbolt.org/z/osra5jfYY.

为什么生成的相同汇编代码不会导致相同的输出?

为什么 void* p = f; 很重要?

Godbolt 向您展示了 运行 使用 -S 编译器生成的程序集。但在这种情况下,这不是实际获得 运行 的代码,因为可以在 link 时间完成进一步优化。

尝试改为选中“编译为二进制”框 (https://godbolt.org/z/ETznv9qP4),这将实际编译和 link 二进制文件,然后反汇编它。我们看到在您的 -DV=f 版本中, f 的代码是:

 addss  xmm0,xmm1
 mulss  xmm0,xmm2
 ret 

和以前一样。但是对于 -DV=0,我们有:

 movss  xmm0,DWORD PTR [rip+0x2d88]
 ret

所以 f 已经转换为一个函数,它只是 return 从内存中加载的常量。在 link 时,编译器能够看到 f 仅使用一组特定的常量参数调用,因此它可以执行过程间常量传播并且 f 仅 return 预先计算的结果。

f 的额外引用显然会破坏这一点。可能编译器或 linker 看到 f 的地址已被占用,但没有注意到该地址从未被处理过。所以它假定 f 可能会在程序的其他地方被调用,因此它必须发出可以为任意参数提供正确结果的代码。

至于为什么结果不一样:预计算是严格的,把a*cb*c都求成float然后相加。所以它的 122457232 结果是 C 规则的“正确”结果,它也是用 -O0-fp-model=strict 编译时得到的结果。 运行时间版本已经优化为(a+b)*c,这实际上更准确,因为它避免了额外的舍入;它产生 122457224,更接近 122457225.

的精确值