Código de muestra ( 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; }Invocación y ejecución (a través de 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+26El código ensamblador generado es el mismo: https://godbolt.org/z/osra5jfYY .
¿Por qué el mismo código ensamblador generado no conduce a la misma salida?
¿Por qué void* p = f; ¿materia?
Godbolt le muestra el ensamblaje emitido al ejecutar el compilador con -S . Pero en este caso, ese no es el código que realmente se ejecuta, porque se pueden realizar más optimizaciones en el momento del enlace.
Intente marcar la casilla "Compilar en binario" en su lugar ( https://godbolt.org/z/ETznv9qP4 ), que en realidad compilará y vinculará el binario y luego lo desensamblará. Vemos que en su versión -DV=f , el código para f es:
addss xmm0,xmm1 mulss xmm0,xmm2 ret como antes Pero con -DV=0 , tenemos:
movss xmm0,DWORD PTR [rip+0x2d88] ret Entonces f se ha convertido en una función que simplemente devuelve una constante cargada desde la memoria. En el momento del enlace, el compilador pudo ver que f solo se llamaba con un conjunto particular de argumentos constantes, por lo que podía realizar una propagación constante entre procedimientos y hacer que f simplemente devolviera el resultado precalculado.
Tener una referencia adicional a f evidentemente derrota esto. Probablemente el compilador o el enlazador ve que se tomó la dirección de f y no se dio cuenta de que nunca se hizo nada con la dirección. Por lo tanto, asume que f podría llamarse en otra parte del programa y, por lo tanto, tiene que emitir un código que dé el resultado correcto para argumentos arbitrarios.
En cuanto a por qué los resultados son diferentes: el precálculo se realiza estrictamente, evaluando tanto a*c como b*c como float y luego añadiéndolos. Entonces, su resultado de 122457232 es el "correcto" según las reglas de C, y también es lo que obtiene al compilar con -O0 o -fp-model=strict . La versión de tiempo de ejecución se ha optimizado a (a+b)*c , que en realidad es más precisa porque evita un redondeo adicional; produce 122457224 , que está más cerca del valor exacto de 122457225 .