Me encontré con esto mientras escribía un programa que intentaba imprimir los valores de bytes constituyentes de los caracteres UTF-8.
Este es el programa que escribí para probar las diversas operaciones ~0 :
#include <stdio.h> int main() { printf("%x\n", (char)~0); // ffffffff printf("%x\n", (unsigned char)~0); // ff printf("%d\n", sizeof(char) == sizeof(unsigned char)); // 1 printf("%d\n", sizeof(char) == sizeof(unsigned int)); // 0 printf("%d\n", (char)~0 == (unsigned int)~0); // 1 } Estoy luchando por entender por qué char produciría un valor de tamaño int , cuando unsigned char produce un valor de tamaño char .
Al pasar un tipo más pequeño que int a una función variable como printf , se promociona a tipo int .
En el primer caso, está pasando char con valor -1 cuya representación (asumiendo el complemento de 2) es 0xff. Esto se promociona a un int con valor -1 y representación 0xffffffff, así que esto es lo que se imprime.
En el segundo caso, está pasando un unsigned char con valor 255 cuya representación es 0xff. Esto se promociona a un int con valor 255 y representación 0x000000ff, por lo que esto es lo que se imprime (sin los ceros iniciales).
No producen valores de diferentes anchos. Producen valores con diferentes números de bits establecidos en ellos.
En su implementación de C, parece que int es de 32 bits y char está firmado. Los usaré en esta respuesta, pero los lectores deben tener en cuenta que el estándar C permite otras opciones.
Usaré hexadecimal para indicar los bits que representan valores.
En (char)~0 , 0 es un int . ~0 luego tiene bits FFFFFFFF. En un complemento a dos de 32 bits int , esto representa −1. (char) convierte esto en char .
En este punto, tenemos un char con valor −1, representado con bits FF. Cuando eso se pasa como argumento a printf , se convierte automáticamente en un int . Como su valor es −1, se convierte en un int con valor −1. Los bits que representan ese int son FFFFFFFF. Le pides a printf que formatee esto con %x . Técnicamente, eso es un error; %x es para un unsigned int , pero su implementación de printf formatea los bits FFFFFFFF como si fueran un unsigned int , produciendo una salida de "ffffffff".
En (unsigned char)~0) , ~0 nuevamente tiene el valor −1 representado con bits FFFFFFFF, pero ahora la conversión es a unsigned char . La conversión a un tipo entero sin signo envuelve el módulo M , donde M es uno más que el valor máximo del tipo, por lo que 256 para un unsigned char de ocho bits. Matemáticamente, la conversión es −1 + 1•256 = 255, que es el valor inicial más el múltiplo de 256 necesario para llevar el valor al rango de unsigned char . El resultado es 255. Prácticamente, se implementa tomando los ocho bits inferiores, por lo que FFFFFFFF se convierte en FF. Sin embargo, en unsigned char , los bits FF representan 255 en lugar de −1.
Ahora tenemos un unsigned char con valor 255, representado con bits FF. Pasar eso a printf da como resultado una conversión automática a un int . Dado que su valor de unsigned char es 255, el resultado de la conversión a int es 255. Cuando le pide a printf que formatee esto con %x (lo cual es un error como el anterior), printf lo formatea como si los bits fueran un unsigned int , produciendo una salida de "ff".
En estas dos llamadas
printf("%x\n", (char)~0); // ffffffff printf("%x\n", (unsigned char)~0); // ff las expresiones (char)~0) y (unsigned char)~0) se convierten al tipo int debido a las promociones de enteros.
En el sistema utilizado, el tipo char se comporta como el tipo signed char . Entonces, el bit de signo en esta expresión se propaga cuando la expresión se promociona al tipo int .
Por otro lado, antes de las promociones de enteros, esta expresión (unsigned char)~0 tiene el tipo unsigned char debido a la conversión al tipo sin signo. Por lo tanto, ningún bit de signo se propaga cuando la expresión se promueve al tipo int .
Preste atención a que el especificador de conversión x se aplica a objetos del tipo unsigned int. Entonces, la primera llamada de printf debe escribirse como
printf("%x\n", ( unsigned int )(char)~0);