¿Cómo habilitar la advertencia del compilador al comparar char y unsigned char?
gcc compiler-warnings (4)
Como i
un signed char
, su valor oscila entre -128 to 127
típicamente. Mientras que count
es un unsigned char
se le asigna el valor 255 (0xFF)
.
Dentro del ciclo, cuando valoro llega a 127
y se incrementa de nuevo, nunca llega a 128
y llega a -128
y luego vuelve a 127
y luego vuelve a -128
, y así sucesivamente. ¡El valor de i
será siempre menor que el valor del count
dentro del bucle y, por lo tanto, el bucle nunca podrá terminar!
Esto está sucediendo debido al desbordamiento del tipo de datos y se debe tener cuidado para examinar críticamente las expresiones donde pueden tener lugar las coerciones automáticas de tipo, ya que no emitirán ninguna advertencia.
EDITAR: De la documentación de GCC,
-Wconversion: advierte sobre las conversiones implícitas que pueden alterar un valor.
Aquí, estamos obteniendo inconsistencia debido a la comparación y no a la asignación.
tome por ejemplo el siguiente código C:
int main(int argc, char *argv[])
{
signed char i;
unsigned char count = 0xFF;
for (i=0; i<count;i++)
{
printf("%x/n", i);
}
return 0;
}
Este código se ejecuta en un bucle infinito, incluso si lo compilo de la siguiente manera:
# gcc -Wall -Wpedantic -Wconversion -Wsign-compare -Wtype-limits -Wsign-conversion test.c -o test
¿Alguien sabe por una bandera del compilador que debería advertir sobre ese tipo de problemas?
Para que quede claro, no estoy preguntando ''¿por qué obtener un ciclo infinito'', sino para saber si hay una manera de evitarlo usando un compilador o análisis estático?
SCHAR_MAX
un signed char
, incrementándolo más allá de que SCHAR_MAX
tenga un efecto definido de implementación. El cálculo i + 1
se realiza después de la promoción de i
a int
y no se desborda (a menos que sizeof(int) == 1
y SCHAR_MAX == INT_MAX
). Sin embargo, este valor está más allá del rango de i
y dado que i
un tipo con signo, o bien el resultado está definido por la implementación o se genera una señal definida por la implementación. (C11 6.3.1.3p3 enteros sin signo).
Por definición, el compilador es la implementación, por lo que el comportamiento se define para cada sistema específico y en las arquitecturas x86 donde almacenar el valor resulta en enmascarar los bits de bajo orden, gcc
debe ser consciente de que la prueba de bucle es definitivamente constante, por lo que es Bucle infinito.
Tenga en cuenta que clang
no detecta la prueba constante tampoco, pero clang 3.9.0
si se count
se declara como const
, y emite una advertencia si i < count
se reemplaza con i < 0xff
, a diferencia de gcc
.
Ninguno de los compiladores se queja del problema de comparación firmado / no firmado porque ambos operandos se promocionan a int
antes de la comparación.
Aquí encontró un problema significativo, especialmente significativo porque algunas convenciones de codificación insisten en usar el tipo más pequeño posible para todas las variables, lo que da como resultado int8_t
como las variables de índice de bucle int8_t
o uint8_t
. Tales elecciones son, de hecho, propensas a errores y aún no he encontrado una manera de hacer que el compilador advierta al programador sobre errores tontos como el que publicaste.
La bandera -Wconversion no captará el error, porque ambos operandos en la comparación: i<count
, se promocionan a int usando las promociones enteras.
No hay bandera en gcc que capte esto.
Aparte de eso, el comportamiento de su código no está definido, porque la variable i se desborda cuando tiene el valor 0x7F
y se incrementa: i++
.
Si desea iterar hasta cierto valor, asegúrese de que el tipo que está utilizando pueda representar ese valor.
No hay ninguna advertencia para detectar esto porque no hay nada cuestionable en este código desde el punto de vista del compilador.
Como se señaló en otras respuestas, la línea de comparación se trata de manera efectiva como
for (i=0; (int)i<(int)count; i++)
Como se describe en Fundamentos de Lenguajes de Programación Estándar Internacional-C , sección 6.3.1.1, eligieron el enfoque de "preservación de valor" para las conversiones de tipo implícitas al comparar valores porque tiene menos posibilidades de obtener resultados de comparación inesperados.
- Tenga en cuenta que su programa se ejecuta "incorrectamente" específicamente porque el resultado de la comparación aritmética es el esperado.
Si esto fuera
int
, esa semántica de "preservación del valor" sería imposible de lograr, porque unint
es, por regla general, un tipo de hardware (o al menos, C está diseñado con esto en mente), y hay pocos (si los hay) arquitecturas que permiten firmar un operando mientras que el otro no está firmado. Esa es la razón principal para que un compilador emita una advertencia de "comparación entre firmados y no firmados" si reemplaza aquí laschar
conint
.
Para la operación de incremento,
i++
,- el mismo razonamiento arriba mencionado menciona en algunos lugares que el "desbordamiento silencioso" es con mucho la semántica más común y esperada. ¡Incluso declaró haber causado "una falta de sensibilidad en la comunidad C a las diferencias entre la aritmética firmada y sin firmar" en primer lugar! Entonces, nada sospechoso aquí tampoco.
En última instancia, lo que causó esta confusión es su inconsciencia hacia la semántica de promoción interna. Lo que provocó que el comportamiento del código fuera inesperado para ti (no te sientas mal, ¡yo tampoco lo sabía antes de leer las otras respuestas!). Sin embargo, resulta que es bastante esperado para el estándar, además, dictado por él. "Ignorar la ley no es excusa", como dicen.