c++ c embedded iar

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))