varias tipos leer imprimir funcion formato datos c variables types bitwise-operators unsigned-char

tipos - ¿Por qué el complemento se comporta de manera diferente a través de printf?



printf java (6)

Estaba leyendo un capítulo sobre operadores bit a bit, me encontré con el programa de operador complementario de 1 y decidí ejecutarlo en Visual C ++.

int main () { unsigned char c = 4, d; d = ~c; printf("%d/n", d); }

Da la salida válida: 251

Luego, en lugar de usar d como una variable para mantener el valor de ~c , decidí imprimir directamente el valor de ~c .

int main () { unsigned char c=4; printf("%d/n", ~c); }

Da la salida -5 .

¿Por qué no funcionó?


Da el op -5. ¿Por qué no funcionó?

En lugar de:

printf("%d",~c);

utilizar:

printf("%d", (unsigned char) ~c);

para obtener el mismo resultado que en su primer ejemplo.

~ operando se somete a una promoción de enteros y la promoción de argumentos por defecto se aplica a argumentos de funciones variadas.


Cuando se aplica el operador ~ a c , se promueve a int , el resultado es también un int .

Entonces

  • en el 1er ejemplo, el resultado se convierte a unsigned char y luego se promociona a signed int e impreso.
  • En el segundo ejemplo, el resultado se imprime como signed int .

En esta declaración:

printf("%d",~c);

la c se convierte al tipo int 1 antes de que se aplique el operador ~ (complemento a nivel de bits). Esto se debe a las promociones de enteros , que se invocan al operando del ~ . En este caso, un objeto de tipo unsigned char se promueve a (firmado) int , que luego (después de ~ evaluación del operador) utiliza la función printf , con el especificador de formato %d correspondiente.

Tenga en cuenta que las promociones de argumentos por defecto (ya que printf es una función variada) no desempeña ningún papel aquí, ya que el objeto ya es de tipo int .

Por otro lado, en este código:

unsigned char c = 4, d; d = ~c; printf("%d", d);

ocurren los siguientes pasos:

  • c está sujeto a promociones de enteros debido a ~ (de la misma manera, como se describió anteriormente)
  • ~c rvalue se evalúa como valor int (con signo) (por ejemplo, -5 )
  • d=~c realiza una conversión implícita de int a unsigned char , ya que d tiene ese tipo. Puede pensar que es lo mismo que d = (unsigned char) ~c . Tenga en cuenta que d no puede ser negativo (esta es una regla general para todos los tipos sin signo).
  • printf("%d", d); invoca promociones de argumentos por defecto , por lo que d se convierte en int y se conserva el valor (no negativo) (es decir, el tipo int puede representar todos los valores del tipo unsigned char ).

1) suponiendo que int puede representar todos los valores del unsigned char (ver el comment de TC a continuación), pero es muy probable que suceda de esta manera. Más específicamente, asumimos que INT_MAX >= UCHAR_MAX es INT_MAX >= UCHAR_MAX . Típicamente, el sizeof(int) > sizeof(unsigned char) contiene y el byte consta de ocho bits. De lo contrario, la c se convertiría en unsigned int (como en la subcláusula C11 §6.3.1.1 / p2), y el especificador de formato también debería cambiarse en consecuencia a %u para evitar obtener un UB (C11 §7.21.6.1 / p9) .


Para comprender el comportamiento de su código, debe aprender el concepto llamado ''Promociones de enteros'' (que sucede en su código implícitamente antes de la operación bit NOT NOT en un operando de caracteres unsigned char ) Como se menciona en el borrador del comité N1570:

§ 6.5.3.3 Operadores aritméticos unarios

  1. El resultado del operador ~ es el complemento a nivel de bit de su operando (promovido) (es decir, cada bit en el resultado se establece si y solo si el bit correspondiente en el operando convertido no se establece). Las promociones de enteros se realizan en el operando y el resultado tiene el tipo promocionado . Si el tipo promocionado es un "''tipo sin signo'', la expresión ~E es equivalente al valor máximo representable en ese tipo menos E ".

Debido a que el tipo de caracteres unsigned char es más estrecho que (ya que requiere menos bytes) tipo int , la promoción de tipo implícita realizada por la máquina abstracta (compilador) y el valor de la variable c se promueven a int en el momento de la compilación (antes de la aplicación de la operación de complemento ~ ) Se requiere para la correcta ejecución del programa porque ~ necesita un operando entero.

§ 6.5 Expresiones

  1. Se requiere que algunos operadores (el operador unario ~ , y los operadores binarios << , >> , & , ^ y | , descritos colectivamente como operadores bit a bit) tengan operandos que tengan un tipo entero . Estos operadores producen valores que dependen de las representaciones internas de los enteros, y tienen aspectos definidos y no definidos de implementación para los tipos con signo.

Los compiladores son lo suficientemente inteligentes como para analizar expresiones, verificar la semántica de las expresiones, realizar verificaciones de tipos y conversiones aritméticas si es necesario. Esa es la razón por la que para aplicar ~ en el tipo char no necesitamos escribir explícitamente ~(int)c - llamado conversión de tipo explícito (y evitar errores).

Nota:

  1. El valor de c se promueve a int en la expresión ~c , pero el tipo de c todavía unsigned char está unsigned char - su tipo no. No te confundas

  2. Importante: ¡el resultado de ~ operación es de tipo int !, Verifique el siguiente código (no tengo compilador vs, estoy usando gcc):

    #include<stdio.h> #include<stdlib.h> int main(void){ unsigned char c = 4; printf(" sizeof(int) = %zu,/n sizeof(unsigned char) = %zu", sizeof(int), sizeof(unsigned char)); printf("/n sizeof(~c) = %zu", sizeof(~c)); printf("/n"); return EXIT_SUCCESS; }

    compilarlo y ejecutar:

    $ gcc -std=gnu99 -Wall -pedantic x.c -o x $ ./x sizeof(int) = 4, sizeof(unsigned char) = 1 sizeof(~c) = 4

    Aviso : el tamaño del resultado de ~c es el mismo que el de int , pero no es igual al unsigned char . ¡El resultado del operador ~ en esta expresión es int ! que como se mencionó 6.5.3.3 Operadores aritméticos unarios

    1. El resultado del operador unario es el negativo de su operando (promocionado). Las promociones de enteros se realizan en el operando y el resultado tiene el tipo promocionado.

Ahora, como @haccks también explicó en su answer , ese resultado de ~c en una máquina de 32 bits y para el valor de c = 4 es:

1111 1111 1111 1111 1111 1111 1111 1011

en decimal es -5 , ¡esa es la salida de su segundo código !

En su primer código , una línea más es interesante para entender b = ~c; , debido a que b es una variable de caracteres unsigned char y el resultado de ~c es de tipo int , por lo que para acomodar el valor del resultado de ~c a b valor de resultado (~ c) se trunca para encajar en el tipo de caracteres sin signo de la siguiente manera:

1111 1111 1111 1111 1111 1111 1111 1011 // -5 & 0xFF & 0000 0000 0000 0000 0000 0000 1111 1111 // - one byte ------------------------------------------- 1111 1011

El equivalente decimal de 1111 1011 es 251 . Puede obtener el mismo efecto usando:

printf("/n ~c = %d", ~c & 0xFF);

o según lo sugerido por @ouah en su answer usando el casting explícito.


Promoción entera, desde el estándar:

Si el tipo de operando con tipo entero con signo puede representar todos los valores del tipo de operando con tipo entero sin signo, el operando con tipo entero sin signo se convertirá en el tipo de operando con tipo entero con signo.


char se promociona a int en la instrucción printf antes de la operación ~ en el segundo fragmento. Entonces c , que es

0000 0100 (2''s complement)

en binario se promueve a (suponiendo una máquina de 32 bits)

0000 0000 0000 0000 0000 0000 0000 0100 // Say it is x

y su complemento en cuanto a bits es igual al complemento de dos del valor menos uno ( ~x = −x − 1 )

1111 1111 1111 1111 1111 1111 1111 1011

que es -5 en decimal en forma de complemento a 2.

Tenga en cuenta que la promoción predeterminada de char c a int también se realiza en

d = ~c;

antes de la operación del complemento, pero el resultado se convierte de nuevo en unsigned char ya que d es del tipo tipo de unsigned char .

C11: 6.5.16.1 Asignación simple (p2):

En la asignación simple ( = ), el valor del operando derecho se convierte al tipo de expresión de asignación y reemplaza el valor almacenado en el objeto designado por el operando izquierdo.

y

6.5.16 (p3):

El tipo de una expresión de asignación es el tipo que tendría el operando izquierdo después de la conversión de valor.