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 asigned 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 valorint
(con signo) (por ejemplo,-5
) -
d=~c
realiza una conversión implícita deint
aunsigned char
, ya qued
tiene ese tipo. Puede pensar que es lo mismo qued = (unsigned char) ~c
. Tenga en cuenta qued
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 qued
se convierte enint
y se conserva el valor (no negativo) (es decir, el tipoint
puede representar todos los valores del tipounsigned 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
- 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 menosE
".
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
- 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:
-
El valor de
c
se promueve aint
en la expresión~c
, pero el tipo dec
todavíaunsigned char
estáunsigned char
- su tipo no. No te confundas -
Importante: ¡el resultado de
~
operación es de tipoint
!, 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 deint
, pero no es igual alunsigned char
. ¡El resultado del operador~
en esta expresión esint
! que como se mencionó 6.5.3.3 Operadores aritméticos unarios- 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.