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

9 minutos de lectura

avatar de usuario
sanketssj5

Estaba leyendo un capítulo sobre operadores bit a bit, me encontré con el programa de operador de complemento 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

Entonces en vez de usar d como una variable para mantener el valor de ~cdecidí imprimir directamente el valor de ~c.

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

Da la salida -5.

¿Por qué no funcionó?

  • Pista: Complemento a 1 de 4 es equivalente a la representación en complemento a dos de -5

    – Vago

    17 de febrero de 2015 a las 10:55

  • es complementarno cumplido

    – phuclv

    18 de febrero de 2015 a las 15:46

  • @LưuVĩnhPhúc tu complemento merece un cumplido.

    – Deja Vu

    02/03/2015 a las 22:57

avatar de usuario
Grzegorz Szpetkowski

En esta declaración:

printf("%d",~c);

la c se convierte en int1 escribe antes de ~ (complemento bit a bit) se aplica el operador. Esto se debe a promociones enterasque se invocan al operando del ~. En este caso un objeto de unsigned char el tipo se promociona a (firmado) intque es entonces (después de ~ evaluación del operador) utilizado por printf función, con correspondencia %d especificador de formato.

Darse cuenta de promociones de argumento predeterminado (como printf es una función variádica) no juega ningún papel aquí, ya que el objeto ya es del tipo int.

Por otro lado, en este código:

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

ocurren los siguientes pasos:

  • c es un sujeto de promociones enteras porque ~ (de la misma manera, como se describe arriba)
  • ~c rvalue se evalúa como (firmado) int valor (por ejemplo -5)
  • d=~c hace una conversión implícita de int a unsigned charcomo d tiene ese tipo. Puedes pensar que es lo mismo que d = (unsigned char) ~c. Darse cuenta de d no puede ser negativo (esta es la regla general para todos los tipos sin firmar).
  • printf("%d", d); invoca promociones de argumento predeterminadode este modo d se convierte en int y el valor (no negativo) se conserva (es decir, el int type puede representar todos los valores de unsigned char escribe).

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

  • Pero luego esto también sucede en d=~c. La diferencia real entonces es la conversión (regreso) a unsigend char eso pasa en d=~cpero no en el printf llamar.

    –Marc van Leeuwen

    17 de febrero de 2015 a las 12:44

  • @MarcvanLeeuwen: El d tiene tipo unsigned charpor lo que no puede ser negativo (incluso después de la conversión a intporque printf‘s promociones de argumento predeterminado). De hecho, tienes razón en que en d=~c la promoción de enteros también ha tenido lugar (de la misma manera, como describí en la respuesta anterior), pero la asignación convierte int a unsigned char de nuevo. Por otra parte, en el segundo caso, printf la función toma int argumento “tal cual” (es decir, %d especificador de formato es correcto), por lo que promociones de argumentos predeterminados no hacer nada allí.

    -Grzegorz Szpetkowski

    17 de febrero de 2015 a las 12:56


  • Para más lectores: actualicé mi respuesta para reflejar la observación de Marc. Puede encontrar la respuesta original (más corta) en el historial de revisiones. Espero que todo esté claro ahora.

    -Grzegorz Szpetkowski

    17/02/2015 a las 19:36

  • Tenga en cuenta que en algunos sistemas unsigned char podría ser promovido a unsigned int por las promociones enteras (por ejemplo, si sizeof(int) == 1), en cuyo caso el printf invocaría un comportamiento indefinido porque utilizó el especificador incorrecto.

    – CT

    18 de febrero de 2015 a las 15:08

  • @TC: Tiene razón, pero supongamos (para simplificar) que vivimos en un mundo donde un byte consta de ocho bits. Sé que el Estándar no lo hace cumplir, pero, por ejemplo, POSIX sí lo hace.

    -Grzegorz Szpetkowski

    18 de febrero de 2015 a las 15:18


avatar de usuario
trucos

char es ascendido a int en printf declaración antes de la operación ~ en segundo fragmento. Asi que ccual es

0000 0100 (2's complement)  

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

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

y su complemento bit a bit es igual al complemento a dos del valor menos uno (~x = −x − 1)

1111 1111 1111 1111 1111 1111 1111 1011  

cual 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 de complemento, pero el resultado se convierte de nuevo a unsigned char como d es de tipo unsigned char.

C11: 6.5.16.1 Asignación simple (p2):

En asignación simple (=), el valor del operando derecho se convierte al tipo de la 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 lvalue.

avatar de usuario
grijesh chauhan

Para entender el comportamiento de su código, necesita aprender el concepto llamado ‘Promociones enteras’ (eso sucede en su código implícitamente antes de la operación bit a bit NO en un unsigned char operando) Como se menciona en el borrador del comité N1570:

§ 6.5.3.3 Operadores aritméticos unarios

  1. el resultado de la ~ operador es el complemento bit a 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 está establecido). Las promociones enteras se realizan en el operando, y el resultado tiene el tipo promovido. Si el tipo promocionado es un ” ‘tipo sin firmar’, la expresión ~E es equivalente al valor máximo representable en ese tipo menos E“.

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

§ 6.5 Expresiones

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

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

Nota:

  1. Valor de c es ascendido a int en expresión ~cpero tipo de c es todavía unsigned char – su tipo no lo hace. No se confunda.

  2. Importante: consecuencia de ~ la operación es de int escriba!, verifique el código a continuación (no tengo vs-compiler, 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;
    }
    

    Compílalo y ejecuta:

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

    Aviso: tamaño del resultado de ~c es igual que de intpero no es igual a unsigned char – consecuencia de ~ operador en esta expresión es int! que como se mencionó 6.5.3.3 Operadores aritméticos unarios

    1. El resultado de la unaria - operador es el negativo de su operando (promovido). Las promociones enteras se realizan en el operando, y el resultado tiene el tipo promocionado.

Ahora, como @haccks también explicó en su respuesta, ese resultado de ~c en una máquina de 32 bits y por 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 tus primer códigouna línea más es interesante de entender b = ~c;porque b es un unsigned char variable y resultado de ~c es de int tipo, para acomodar el valor del resultado de ~c a b el valor del resultado (~c) es truncado para encajar en el tipo de carácter sin firmar como sigue:

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

Equivalente decimal de 1111 1011 es 251. Podrías obtener el mismo efecto usando:

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

o como lo sugiere @ouah en su respuesta usando casting explícito.

  • @haccks gracias, sí, principalmente vengo aquí para aprender más que para participar y actualmente estoy trabajando principalmente en python, django, productos web.

    –Grijesh Chauhan

    17 de febrero de 2015 a las 17:54


  • +int(PI/3) por citar realmente las partes de especificaciones relevantes… ninguna otra respuesta explica realmente por qué la promoción pasa aquí! esto->Some operators (the unary operator ~, and the binary operators <<, >>, &, ^, and |, collectively described as bitwise operators) are required to have operands that have integer type. hizo mi día hoy.

    usuario719662

    18 de febrero de 2015 a las 9:25


  • @vaxquis gracias, cuando llegué a esta publicación me di cuenta de que la mayoría de las respuestas explicaban “en qué se diferencian los resultados”, así que decidí agregar mi respuesta y enfatizar la razón de “por qué”, en el momento en que leíste mi respuesta me faltaba uno más relevante del borrador, ahora agregado.

    –Grijesh Chauhan

    18 de febrero de 2015 a las 15:09

Al aplicar el ~ operador a c se promociona a intel resultado es un int también.

Después

  • en el primer ejemplo, el resultado se convierte en unsigned char y luego promovido a signed int e impreso.
  • en el segundo ejemplo, el resultado se imprime como signed int.

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

En vez de:

printf("%d",~c);

usar:

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

para obtener el mismo resultado que en su primer ejemplo.

~ El operando se somete a la promoción de enteros y la promoción de argumentos predeterminada se aplica al argumento de las funciones variádicas.

avatar de usuario
david ranieri

Promoción de enteros, del estándar:

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

¿Ha sido útil esta solución?