c++ - Optimización del compilador de operación bit a bit
embedded iar (3)
Como ya señalaron Lundin y dbush, la comparación en la segunda versión siempre falla porque lo contrario de cualquier valor de
uint8
promovido a
int
es diferente de todos los valores de
uint8
.
En otras palabras, la segunda versión es equivalente a:
// this does NOT work as expected (I only removed the tmp!)
uint8 verifyInverseBuffer(uint8 *buf, uint8 *bufi, uint32 len) {
if (len) return 0;
return 1;
}
Como se puede ver en
el explorador del compilador de Godbolt
, tanto
gcc
como
clang
detectan esto y optimizan el código por completo:
verifyInverseBuffer:
test edx, edx
sete al
ret
gcc
produce una advertencia bastante críptica, señalando un problema de comparación sospechoso firmado / no firmado que no es el problema real ... Cierre pero no banana.
<source>: In function ''verifyInverseBuffer'':
<source>:8:16: warning: comparison of promoted bitwise complement of an unsigned value with unsigned [-Wsign-compare]
8 | if (buf[i] != (~bufi[i]))
| ^~
Compiler returned: 0
Tengo una función simple que prueba si dos matrices son inversas entre sí.
Son aparentemente idénticos, excepto por una variable
tmp
.
Uno funciona y el otro no.
Por mi vida no puedo entender por qué el compilador optimizaría esto, si es realmente un problema de optimización (mi compilador es IAR Workbench v4.30.1).
Aquí está mi código:
// this works as expected
uint8 verifyInverseBuffer(uint8 *buf, uint8 *bufi, uint32 len)
{
uint8 tmp;
for (uint32 i = 0; i < len; i++)
{
tmp = ~bufi[i];
if (buf[i] != tmp)
{
return 0;
}
}
return 1;
}
// this does NOT work as expected (I only removed the tmp!)
uint8 verifyInverseBuffer(uint8 *buf, uint8 *bufi, uint32 len)
{
for (uint32 i = 0; i < len; i++)
{
if (buf[i] != (~bufi[i]))
{
return 0;
}
}
return 1;
}
La primera versión del código funciona, la segunda no. ¿Alguien puede entender por qué? ¿O viene con algunas pruebas para investigar qué está mal?
El problema es la promoción de enteros.
¡El operador
~
es muy peligroso!
En el caso de
~bufi[i]
, el operando de
~
se promociona de acuerdo con las promociones de enteros.
Hacer el código equivalente a
~(int)bufi[i]
.
Entonces, en el segundo caso
buf[i] != (~bufi[i])
obtienes algo como
0xXX != 0xFFFFFFFFYY
, donde "XX" e "YY" son los valores reales que deseas comparar y 0xFFFF es una basura no deseada colocada allí tomando el complemento bit a bit de un
int
.
Esto siempre se evaluará como
true
para que el compilador pueda optimizar partes del código, creando un error muy sutil.
En el caso de
tmp = ~bufi[i];
esquiva este error truncando
0xFFFFFFFFYY
en "YY", el valor que le interesa.
Consulte las reglas de promoción de tipo implícito para más detalles. También considere adoptar MISRA-C para esquivar errores sutiles como este.
Lo que ves que sucede es el resultado de las reglas de las
promociones
de
enteros
.
Cada vez que se utiliza una variable más pequeña que una
int
en una expresión, el valor se promueve para escribir
int
.
Supongamos que
bufi[i]
contiene el valor 255. La representación hexadecimal de esto es
0xFF
.
Este valor es entonces operando del operador
~
.
Por lo tanto, el valor primero se promocionará a
int
que (suponiendo que sea de 32 bits) tendrá el valor
0x000000FF
, y la aplicación de
~
a esto le da
0xFFFFFF00
.
Luego compara este valor con
buf[i]
que es del tipo
uint8_t
.
El valor
0xFFFFFF00
está fuera de este rango, por lo que la comparación siempre será falsa.
Si asigna el resultado de
~
back a una variable de tipo
uint8_t
, el valor
0xFFFFFF00
se convierte a
0x00
.
Es este valor convertido el que luego se compara con
buf[i]
.
Entonces, el comportamiento que ves no es el resultado de una optimización sino las reglas del lenguaje.
Usar una variable temporal tal como es es una forma de abordar este problema.
También puede
uint8
el resultado a
uint8
:
if(buf[i] != (uint8)(~bufi[i]))
O enmascarar todos menos el byte de orden más bajo:
if(buf[i] != (~bufi[i] & 0xff))