Conversión de tipo: sin signo para firmar int / char

Intenté ejecutar el siguiente progtwig:

#include  int main() { signed char a = -5; unsigned char b = -5; int c = -5; unsigned int d = -5; if (a == b) printf("\r\n char is SAME!!!"); else printf("\r\n char is DIFF!!!"); if (c == d) printf("\r\n int is SAME!!!"); else printf("\r\n int is DIFF!!!"); return 0; } 

Para este progtwig, estoy obteniendo el resultado:

char es DIFF !!! int es MISMO !!!

¿Por qué estamos obteniendo resultados diferentes para ambos?
¿Debería el resultado ser el siguiente?

char es MISMO !!! int es MISMO !!!

Un enlace de teclado .

Esto se debe a las diversas reglas de conversión de tipo implícitas en C. Hay dos de ellas que un progtwigdor en C debe conocer: las conversiones aritméticas habituales y las promociones enteras (estas últimas son parte de la primera).

En el caso char tienes los tipos (signed char) == (unsigned char) . Estos son ambos tipos enteros pequeños . Otros tipos de enteros pequeños son bool y short . Las reglas de promoción de enteros establecen que siempre que un tipo entero pequeño sea un operando de una operación, su tipo se promocionará a int , que se firmará. Esto sucederá sin importar si el tipo fue firmado o sin firmar.

En el caso del signed char , el signo se conservará y se promocionará a un int contenga el valor -5. En el caso del unsigned char , contiene un valor que es 251 (0xFB). Se promocionará a un int que contenga el mismo valor. Terminas con

 if( (int)-5 == (int)251 ) 

En el caso entero, tiene los tipos (signed int) == (unsigned int) . No son tipos enteros pequeños, por lo que las promociones enteras no se aplican. En su lugar, se equilibran con las conversiones aritméticas habituales , que establecen que si dos operandos tienen el mismo “rango” (tamaño) pero diferente firma, el operando con signo se convierte al mismo tipo que el sin signo. Terminas con

 if( (unsigned int)-5 == (unsigned int)-5) 

Buena pregunta!

La comparación int funciona, porque ambos ints contienen exactamente los mismos bits, por lo que son esencialmente los mismos. Pero ¿y los char ?

Ah, C promueve implícitamente char s to int s en varias ocasiones. Este es uno de ellos. Su código dice if(a==b) , pero a lo que el comstackdor realmente le da eso es a:

 if((int)a==(int)b) 

(int)a es -5, pero (int)b es 251. Definitivamente no son lo mismo.

EDITAR: Como @ Acido carbónico-señalado, (int)b es 251 solo si un char tiene 8 bits de largo. Si int tiene 32 bits de longitud, (int)b es -32764.

REDIT: hay un montón de comentarios sobre la naturaleza de la respuesta si un byte no tiene 8 bits de longitud. La única diferencia en este caso es que (int)b no es 251 sino un número positivo diferente, que no es -5. Esto no es realmente relevante para la pregunta que todavía es muy buena.

Bienvenido a la promoción de enteros . Si puedo citar desde el sitio web:

Si un int puede representar todos los valores del tipo original, el valor se convierte en un int; de lo contrario, se convierte a unsigned int. Estas se llaman promociones enteras. Todos los otros tipos no se modifican por las promociones enteras.

C puede ser realmente confuso cuando haces comparaciones como estas, recientemente desconcerté a algunos de mis amigos de progtwigción que no son C con la siguiente burla:

 #include  #include  int main() { char* string = "One looooooooooong string"; printf("%d\n", strlen(string)); if (strlen(string) < -1) printf("This cannot be happening :("); return 0; } 

Que de hecho se imprime This cannot be happening :( y aparentemente demuestra que 25 es más pequeño que -1!

Sin embargo, lo que sucede debajo es que -1 se representa como un entero sin signo que, debido a la representación de los bits subyacentes, es igual a 4294967295 en un sistema de 32 bits. Y, naturalmente, 25 es más pequeño que 4294967295.

Sin embargo, si lanzamos explícitamente el tipo size_t devuelto por strlen como un entero con signo:

 if ((int)(strlen(string)) < -1) 

Luego, comparará 25 contra -1 y todo estará bien con el mundo.

Un buen comstackdor debería advertirle acerca de la comparación entre un entero sin signo y con signo, y aún así es tan fácil de perder (especialmente si no habilita las advertencias).

Esto es especialmente confuso para los progtwigdores de Java ya que todos los tipos primitivos están firmados. Esto es lo que James Gosling (uno de los creadores de Java) tenía que decir sobre el tema :

Gosling: Para mí, como diseñador de lenguaje, en lo que realmente no me cuento en estos días, lo que "simple" realmente terminó en significado fue si podía esperar que J. Random Developer mantuviera las especificaciones en su cabeza. Esa definición dice que, por ejemplo, Java no lo es, y de hecho, muchos de estos idiomas terminan con muchos casos de esquina, cosas que nadie entiende realmente. Cuestione cualquier desarrollador de C sobre unsigned, y muy pronto descubrirá que casi ningún desarrollador C en realidad entiende lo que sucede con unsigned, lo que es la aritmética unsigned. Cosas como esa hicieron que C sea complejo. La parte de lenguaje de Java es, creo, bastante simple. Las bibliotecas que debes buscar

La representación hexadecimal de -5 es:

  • 8 bits, complementos de dos caracteres signed char : 0xfb
  • 32 bits, complemento de dos signed int : 0xfffffffb

Cuando convierte un número firmado a un número sin firmar, o viceversa, el comstackdor no … precisa nada. que hay para hacer? El número es convertible o no lo es, en cuyo caso sigue el comportamiento indefinido o definido por la implementación (realmente no he comprobado cuál) y el comportamiento más eficiente definido por la implementación es no hacer nada.

Entonces, la representación hexadecimal de (unsigned )-5 es:

  • unsigned char 8 bits unsigned char : 0xfb
  • 32 bits, unsigned int : 0xfffffffb

¿Parecer familiar? Son bits-por-bit lo mismo que las versiones firmadas.

Cuando escribe if (a == b) , donde a y b son de tipo char , lo que el comstackdor realmente debe leer es if ((int)a == (int)b) . (Esta es la “promoción entera” de la que todos los demás están hablando).

Entonces, ¿qué sucede cuando convertimos char a int ?

  • signed char 8 bits con signed char de 32 bits signed int : 0xfb -> 0xfffffffb
    • ¡Bien, eso tiene sentido porque coincide con las representaciones de -5 arriba!
    • Se llama “extensión de signo” porque copia el bit superior del byte, el “bit de signo”, hacia la izquierda en el nuevo valor más amplio.
  • 0xfb 8 bits unsigned char a 32 bits con la signed int : 0xfb -> 0x000000fb
    • Esta vez realiza una “extensión cero” porque el tipo de fuente no está firmado , por lo que no hay ningún bit de signo para copiar.

Entonces, a == b realmente hace 0xfffffffb == 0x000000fb => ¡no coincide!

Y, c == d realmente hace 0xfffffffb == 0xfffffffb => partido!

Mi punto es: ¿no recibiste una advertencia en el momento de la comstackción “comparando la expresión firmada y no firmada”?

¡El comstackdor intenta informarte que tiene derecho a hacer cosas locas! 🙂 Añadiría, cosas locas sucederán usando valores grandes, cerca de la capacidad del tipo primitivo. Y

  unsigned int d = -5; 

está asignando definitivamente un gran valor a d, es equivalente (incluso si, probablemente no se garantiza que sea equivalente) a ser:

  unsigned int d = UINT_MAX -4; ///Since -1 is UINT_MAX 

Editar:

Sin embargo, es interesante notar que solo la segunda comparación da una advertencia (verifique el código) . Entonces, significa que el comstackdor que aplica las reglas de conversión confía en que no habrá errores en la comparación entre unsigned char y char (durante la comparación se convertirán a un tipo que pueda representar con seguridad todos sus valores posibles). Y él tiene razón en este punto. Luego, le informa que este no será el caso para unsigned int e int : durante la comparación uno de los 2 se convertirá a un tipo que no puede representarlo por completo.

Para completarlo, lo comprobé también para abreviar : el comstackdor se comporta de la misma manera que para los caracteres, y, como se esperaba, no hay errores en el tiempo de ejecución.

.

Relacionado con este tema, hace poco hice esta pregunta (sin embargo, orientado a C ++).