Contexto
Estamos transfiriendo código C que se compiló originalmente usando un compilador C de 8 bits para el microcontrolador PIC. Un modismo común que se usó para evitar que las variables globales sin firmar (por ejemplo, contadores de errores) vuelvan a cero es el siguiente:
if(~counter) counter++; El operador bit a bit aquí invierte todos los bits y la declaración solo es verdadera si el counter es menor que el valor máximo. Es importante destacar que esto funciona independientemente del tamaño de la variable.
Problema
Ahora apuntamos a un procesador ARM de 32 bits usando GCC. Hemos notado que el mismo código produce resultados diferentes. Por lo que podemos decir, parece que la operación de complemento bit a bit devuelve un valor que tiene un tamaño diferente al que esperaríamos. Para reproducir esto, compilamos, en GCC:
uint8_t i = 0; int sz; sz = sizeof(i); printf("Size of variable: %d\n", sz); // Size of variable: 1 sz = sizeof(~i); printf("Size of result: %d\n", sz); // Size of result: 4 En la primera línea de salida, obtenemos lo que esperaríamos: i es 1 byte. Sin embargo, el complemento bit a bit de i es en realidad cuatro bytes , lo que causa un problema porque las comparaciones con esto ahora no darán los resultados esperados. Por ejemplo, si está haciendo (donde i es un uint8_t correctamente inicializado):
if(~i) i++; veremos i "cambio" de 0xFF a 0x00. Este comportamiento es diferente en GCC en comparación con cuando solía funcionar como pretendíamos en el compilador anterior y el microcontrolador PIC de 8 bits.
Somos conscientes de que podemos resolver esto emitiendo así:
if((uint8_t)~i) i++;o por
if(i < 0xFF) i++; sin embargo, en ambas soluciones, el tamaño de la variable debe conocerse y es propenso a errores para el desarrollador de software. Este tipo de comprobaciones de límites superiores se producen en todo el código base. Hay varios tamaños de variables (p. ej., uint16_t y caracteres unsigned char , etc.) y cambiarlos en un código base que funciona de otro modo no es algo que estemos esperando.
Pregunta
¿Es correcta nuestra comprensión del problema? ¿Hay opciones disponibles para resolverlo que no requieran volver a visitar cada caso en el que hayamos usado este modismo? ¿Es correcta nuestra suposición de que una operación como el complemento bit a bit debería devolver un resultado del mismo tamaño que el operando? Parece que esto se rompería, dependiendo de las arquitecturas del procesador. Siento que estoy tomando pastillas locas y que C debería ser un poco más portátil que esto. Una vez más, nuestra comprensión de esto podría estar equivocada.
En la superficie, esto puede no parecer un gran problema, pero este idioma que funcionaba anteriormente se usa en cientos de ubicaciones y estamos ansiosos por comprender esto antes de proceder con cambios costosos.
Nota: aquí hay una pregunta duplicada aparentemente similar pero no exacta: la operación bit a bit en char da un resultado de 32 bits
No vi el quid real del problema discutido allí, es decir, el tamaño del resultado de un complemento bit a bit es diferente de lo que se pasa al operador.
en tamaño de (i); solicita el tamaño de la variable i , por lo que 1
en tamaño de(~i); solicitas el tamaño del tipo de la expresión, que es un int , en tu caso 4
Usar
si (~ yo)
para saber si no valoro 255 (en tu caso con un uint8_t ) no es muy legible, solo hazlo
if (i != 255)y tendrás un código portátil y legible
Hay varios tamaños de variables (p. ej., uint16_t y char sin firmar, etc.)
Para administrar cualquier tamaño de unsigned :
if (i != (((uintmax_t) 2 << (sizeof(i)*CHAR_BIT-1)) - 1))La expresión es constante, por lo que se calcula en tiempo de compilación.
#include <limits.h> para CHAR_BIT e #include <stdint.h> para uintmax_t