Contexto
Estamos transfiriendo el código C que se compiló originalmente usando un compilador C de 8 bits para el microcontrolador PIC. Un modismo común que se utilizó para evitar que las variables globales sin signo (por ejemplo, contadores de error) 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 counter
es menor que el valor máximo. Es importante destacar que esto funciona independientemente del tamaño variable.
Problema
Ahora estamos apuntando a un procesador ARM de 32 bits usando GCC. Hemos notado que el mismo código produce resultados diferentes. Hasta donde podemos ver, parece que la operación de complemento a nivel de bits devuelve un valor que es de 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 i
es en realidad de 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
se inicializa correctamente uint8_t
):
if(~i) i++;
Veremos i
"ajuste" desde 0xFF hasta 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 lanzando así:
if((uint8_t)~i) i++;
O por
if(i < 0xFF) i++;
Sin embargo, en estas dos soluciones, el tamaño de la variable debe ser conocido y es propenso a errores para el desarrollador de software. Este tipo de comprobaciones de límites superiores se producen en toda la base de código. Hay varios tamaños de variables (por ejemplo., uint16_t
Y unsigned char
etc.) y el cambio de éstos en una base de código de otra forma de trabajo no es algo que estamos deseando.
Pregunta
¿Nuestra comprensión del problema es correcta, y hay opciones disponibles para resolver esto que no requieren volver a visitar cada caso en el que hemos usado este idioma? ¿Es correcta nuestra suposición de que una operación como complemento a nivel de bits debería devolver un resultado que tenga el 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. Nuevamente, 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 entender 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 de la cuestión discutido allí, a saber, el tamaño del resultado de un complemento bit a bit diferente de lo que se pasa al operador.
fuente
Respuestas:
Lo que está viendo es el resultado de promociones enteras . En la mayoría de los casos en los que se usa un valor entero en una expresión, si el tipo del valor es más pequeño de lo que
int
se promueveint
. Esto se documenta en la sección 6.3.1.1p2 del estándar C :Entonces, si una variable tiene tipo
uint8_t
y el valor 255, el uso de cualquier operador que no sea una conversión o asignación en ella primero la convertirá a tipoint
con el valor 255 antes de realizar la operación. Es por eso quesizeof(~i)
le da 4 en lugar de 1.La Sección 6.5.3.3 describe que las promociones de enteros se aplican al
~
operador:Entonces, suponiendo un valor de 32 bits
int
, sicounter
tiene el valor de 8 bits0xff
, se convierte en el valor de 32 bits0x000000ff
, y aplicarlo~
le da0xffffff00
.Probablemente, la forma más sencilla de manejar esto es sin tener que saber el tipo es verificar si el valor es 0 después de incrementar, y si es así disminuirlo.
La envoltura de los enteros sin signo funciona en ambas direcciones, por lo que disminuir un valor de 0 le da el mayor valor positivo.
fuente
if (!++counter) --counter;
podría ser menos extraño para algunos programadores que usar el operador de coma.++counter; counter -= !counter;
.increment_unsigned_without_wraparound
oincrement_with_saturation
. Personalmente, usaría una función genérica de tres operandosclamp
.en tamaño de (i); solicita el tamaño de la variable i , entonces 1
en tamaño de (~ i); solicita el tamaño del tipo de expresión, que es un int , en su caso 4
Usar
saber si yo no valora 255 (en su caso con un el uint8_t) no es muy legible, acaba de hacer
y tendrás un código portátil y legible
Para administrar cualquier tamaño de sin firmar:
La expresión es constante, por lo que se calcula en tiempo de compilación.
#include <limits.h> para CHAR_BIT y #include <stdint.h> para uintmax_t
fuente
!= 255
es inadecuado.unsigned
objetos, ya que los desplazamientos de todo el ancho del objeto no están definidos por el estándar C, pero se puede solucionar con(2u << sizeof(i)*CHAR_BIT-1) - 1
.((uintmax_t) 2 << sizeof(i)*CHAR_BIT-1) - 1
.Aquí hay varias opciones para implementar "Agregar 1 a
x
pero fijar en el valor máximo representable", dado quex
es un tipo entero sin signo:Agregue uno si y solo si
x
es menor que el valor máximo representable en su tipo:Consulte el siguiente elemento para la definición de
Maximum
. Este método tiene una buena posibilidad de ser optimizado por un compilador para obtener instrucciones eficientes, como una comparación, alguna forma de conjunto o movimiento condicional, y un complemento.Compare con el valor más grande del tipo:
(Esto calcula 2 N , donde N es el número de bits
x
, al cambiar 2 por N −1 bits. Hacemos esto en lugar de cambiar 1 N bits porque un cambio por el número de bits en un tipo no está definido por C estándar. LaCHAR_BIT
macro puede ser desconocida para algunos; es el número de bits en un byte, también losizeof x * CHAR_BIT
es el número de bits en el tipo dex
).Esto se puede envolver en una macro según se desee por estética y claridad:
Incremente
x
y corrija si se ajusta a cero, usando unif
:Incremente
x
y corrija si se ajusta a cero, usando una expresión:Esto es nominalmente sin ramificación (a veces beneficioso para el rendimiento), pero un compilador puede implementarlo de la misma manera anterior, utilizando una ramificación si es necesario pero posiblemente con instrucciones incondicionales si la arquitectura de destino tiene instrucciones adecuadas.
Una opción sin ramificación, utilizando la macro anterior, es:
Si
x
es el máximo de su tipo, esto se evalúa comox += 1-1
. De lo contrario, lo esx += 1-0
. Sin embargo, la división es algo lenta en muchas arquitecturas. Un compilador puede optimizar esto a instrucciones sin división, dependiendo del compilador y la arquitectura de destino.fuente
-Wshift-op-parentheses
. La buena noticia es que un compilador optimizador no va a generar una división aquí, por lo que no tiene que preocuparse de que sea lento.sizeof x
no se puede implementar dentro de una función C porquex
debería ser un parámetro (u otra expresión) con algún tipo fijo. No pudo producir el tamaño del tipo de argumento que usa la persona que llama. Una lata de macro.Antes de stdint.h, los tamaños de las variables pueden variar de un compilador a otro y los tipos de variables reales en C siguen siendo int, long, etc. y el autor del compilador los define como su tamaño. No algunos supuestos específicos estándar o específicos. El (los) autor (es) deben crear stdint.h para mapear los dos mundos, ese es el propósito de stdint.h para mapear uint_this that a int, long, short.
Si está transfiriendo código de otro compilador y usa char, short, int, long, debe pasar por cada tipo y realizar el puerto usted mismo, no hay forma de evitarlo. Y si termina con el tamaño correcto para la variable, la declaración cambia pero el código como está escrito funciona ...
o ... suministre la máscara o el tipografía directamente
Al final del día, si desea que este código funcione, debe portarlo a la nueva plataforma. Tu elección de cómo. Sí, debe dedicar el tiempo necesario a cada caso y hacerlo bien; de lo contrario, volverá a este código, que es aún más costoso.
Si aísla los tipos de variables en el código antes de portarlos y el tamaño de los tipos de variables, entonces aísle las variables que hacen esto (debería ser fácil de asimilar) y cambie sus declaraciones usando definiciones stdint.h que, con suerte, no cambiarán en el futuro, y te sorprenderías, pero a veces se usan los encabezados incorrectos, así que incluso pon cheques para que puedas dormir mejor por la noche
Y si bien ese estilo de codificación funciona (if (~ counter) counter ++;), para la portabilidad desea ahora y en el futuro, es mejor usar una máscara para limitar específicamente el tamaño (y no confiar en la declaración), haga esto cuando el código se escribe en primer lugar o simplemente termina el puerto y luego no tendrás que volver a portarlo algún otro día. O para que el código sea más legible, haga x <0xFF entonces o x! = 0xFF o algo así, entonces el compilador puede optimizarlo en el mismo código que cualquiera de estas soluciones, solo lo hace más legible y menos riesgoso ...
Depende de lo importante que sea el producto o cuántas veces desee enviar parches / actualizaciones o hacer rodar un camión o caminar al laboratorio para solucionar el problema, ya sea que intente encontrar una solución rápida o simplemente toque las líneas de código afectadas. si solo son cien o pocos, no es un puerto tan grande.
fuente
C 2011 Borrador en línea
El problema es que el operando de
~
se está promoviendoint
antes de que se aplique el operador.Desafortunadamente, no creo que haya una manera fácil de salir de esto. Escritura
no ayudará porque las promociones se aplican allí también. Lo único que puedo sugerir es crear algunas constantes simbólicas para el valor máximo que desea que represente ese objeto y probar eso:
fuente
-1
no es necesario, ya que esto provocaría que el contador se establezca en 254 (0xFE). En cualquier caso, este enfoque, como se mencionó en mi pregunta, no es ideal debido a los diferentes tamaños variables en la base de código que participan en este idioma.