Pregunta

Aquí hay dos programas muy simples. Esperaría obtener la misma salida, pero no. No puedo entender por qué. Las primeras salidas 251. las segundas salidas -5. Puedo entender por qué el 251. Sin embargo, no veo por qué el segundo programa me da un -5.

Programa 1:

#include <stdio.h>

int main()
{

unsigned char  a;
unsigned char  b;
unsigned int  c;

a = 0;
b= -5;

c =  (a + b);

printf("c hex: %x\n", c);
printf("c dec: %d\n",c);

}

Producción:

c hex: fb
c dec: 251

Programa 2:

#include <stdio.h>

int main()
{

unsigned char  a;
unsigned char  b;
unsigned int  c;

a = 0;
b=  5;

c =  (a - b);

printf("c hex: %x\n", c);
printf("c dec: %d\n",c);

}

Producción:

c hex: fffffffb
c dec: -5
¿Fue útil?

Solución

Hay dos problemas separados aquí. El primero es el hecho de que está obteniendo diferentes valores hexadecimales para lo que parece las mismas operaciones. El hecho subyacente que te estás perdiendo es que chars son promovidos a ints (como lo son shorts) hacer aritmética. Aquí está la diferencia:

a = 0  //0x00
b = -5 //0xfb
c = (int)a + (int)b

Aquí, a se extiende a 0x00000000 y b se extiende a 0x000000fb (no signo extendido, porque es un no firmado carbonizarse). Entonces, la adición se realiza y obtenemos 0x000000fb.

a = 0  //0x00
b = 5  //0x05
c = (int)a - (int)b

Aquí, a se extiende a 0x00000000 y b se extiende a 0x00000005. Entonces, la resta se realiza y obtenemos 0xfffffffb.

¿La solución? Seguir con chars o ints; Mezclarlos puede causar cosas que no esperarás.

El segundo problema es que un unsigned int se está imprimiendo como -5, claramente un valor firmado. Sin embargo, en la cadena, le dijiste printf para imprimir su segundo argumento, interpretado como un int firmado (eso es lo que "%d" medio). El truco aquí es que printf No sabe en qué tipos de las variables que pasó. Simplemente las interpreta en la forma en que la cadena le dice. Aquí hay un ejemplo en el que lo contamos printf para imprimir un puntero como int:

int main()
{
    int a = 0;
    int *p = &a;
    printf("%d\n", p);
}

Cuando ejecuto este programa, obtengo un valor diferente cada vez, que es la ubicación de memoria de a, convertido a la base 10. Puede notar que este tipo de cosas causa una advertencia. Debe leer todas las advertencias que le brinda su compilador, y solo ignorarlas si está completamente seguro de que está haciendo lo que pretende.

Otros consejos

En el primer programa, b=-5; asigna 251 a b. (Las conversiones a un tipo sin firmar siempre reducen el Valor Modulo One más el valor máximo del tipo de destino).

En el segundo programa, b=5; simplemente asigna 5 a b, después c = (a - b); realiza la resta 0-5 como tipo int Debido a las promociones predeterminadas, en pocas palabras, "más pequeño que int"Los tipos siempre se promueven a int antes de ser utilizado como operandos de operadores aritméticos y bit a bit a bit.

Editar: Una cosa que me perdí: desde c tiene tipo unsigned int, el resultado -5 en el segundo programa se convertirá en unsigned int Cuando la asignación a c se realiza, lo que resulta en UINT_MAX-4. Esto es lo que ves con el %x especificador a printf. Al imprimir c con %d, obtienes un comportamiento indefinido, porque %d espera un (firmado) int argumento y pasaste un unsigned int argumento con un valor que no es representable en simple (firmado) int.

Estás usando el especificador de formato %d. Que trata el argumento como un número decimal firmado (básicamente int).

Obtienes 251 del primer programa porque (unsigned char)-5 es 251, entonces lo imprime como un dígito decimal firmado. Se promueve a 4 bytes en lugar de 1, y esos bits son 0, entonces el número parece 0000...251 (donde el 251 es binario, simplemente no lo convirtí).

Obtienes -5 del segundo programa porque (unsigned int)-5 es un gran valor, pero lanzado a un int, su -5. Se trata como un int debido a la forma en que usa printf.

Use el especificador de formato %ud para imprimir valores decimales sin firmar.

Lo que estás viendo es el resultado de cómo la máquina subyacente representa los números Cómo el estándar C define firmado en conversiones de tipo sin firmar (para la aritmética) y cómo la máquina subyacente representa los números (para el resultado del comportamiento indefinido al final).

Cuando originalmente escribí mi respuesta, asumí que el estándar C no definía explícitamente cómo los valores firmados deberían convertirse en valores sin firmar, ya que El estándar no define cómo se deben representar los valores firmados o cómo convertir los valores no firmados en valores firmados cuando el rango está fuera del tipo firmado.

Sin embargo, resulta que el estándar define explícitamente que al convertir de valores negativos con signo a positivos no firmados. En el caso de un entero, un valor negativo firmado x se convertirá a uint_max+1-x, como si se almacenara como un valor firmado en el complemento de dos y luego se interpretó como un valor sin firmar.

Entonces cuando dices:

unsigned char  a;
unsigned char  b;
unsigned int c;

a = 0; 
b = -5;
c = a + b;

El valor de B se convierte en 251, porque -5 se convierte en un tipo de valor sin firmar UCHAR_MAX-5+1 (255-5+1) usando el estándar C. Es entonces después de esa conversión que la adición tiene lugar. Eso hace a A + B igual que 0 + 251, que luego se almacena en c. Sin embargo, cuando dices:

unsigned char  a;
unsigned char  b;
unsigned int c;

a = 0;
b = 5;
c = (a-b);

printf("c dec: %d\n", c);

En este caso, A y B son promovidos a ints sin firmar, para que coincidan con C, por lo que permanecen 0 y 5 en valor. Sin embargo, 0 - 5 en matemáticas enteras sin firmar conduce a un error de subflujo, que se define para dar como resultado UINT_MAX+1-5. Si esto hubiera sucedido antes de la promoción, el valor sería UCHAR_MAX+1-5 (es decir, 251 nuevamente).

Sin embargo, la razón por la que ve -5 impresas en su salida es una combinación del hecho de que el entero sin firmar UINT_MAX -4 y -5 tienen la misma representación binaria exacta, al igual que -5 y 251 con un tipo de datos de un solo byte, y El hecho de que cuando usó "%D" como cadena de formato, que le dijo a Printf que interpretara el valor de C como un entero firmado en lugar de un entero sin firmar.

Dado que no se define una conversión de valores sin firmar a valores firmados para valores no válidos, el resultado se convierte en una implementación específica. En su caso, dado que la máquina subyacente usa el complemento de dos para los valores firmados, el resultado es que el valor sin firmar UINT_MAX -4 se convierte en el valor firmado -5.

La única razón por la que esto no sucede en el primer programa porque un INT sin firmar y un INT firmado pueden representar 251, por lo que la conversión entre los dos está bien definida y el uso de "%D" o "%U" no importa. Sin embargo, en el segundo programa, resulta en un comportamiento indefinido y se convierte en una implementación específica ya que su valor de UINT_MAX-4 salió del rango de un INT firmado.

¿Qué está pasando debajo del capó?

Siempre es bueno verificar lo que crees que está sucediendo o lo que debería suceder con lo que realmente está sucediendo, así que veamos la salida del lenguaje de ensamblaje del compilador ahora para ver exactamente lo que está sucediendo. Aquí está la parte significativa del primer programa:

    mov     BYTE PTR [rbp-1], 0   ; a becomes 0
    mov     BYTE PTR [rbp-2], -5  ; b becomes -5, which as an unsigned char is also 251
    movzx   edx, BYTE PTR [rbp-1] ; promote a by zero-extending to an unsigned int, which is now 0
    movzx   eax, BYTE PTR [rbp-2] ; promote b by zero-extending to an unsigned int which is now 251
    add     eax, edx  ; add a and b, that is, 0 and 251

Tenga en cuenta que aunque almacenamos un valor firmado de -5 en el byte B, cuando el compilador lo promueve, lo promueve al extender cero el número, lo que significa que se interpreta como el valor no firmado que representa 11111011 en lugar del valor firmado. Luego, los valores promovidos se suman para convertirse en c. Esta es también la razón por la cual el estándar C define firmado a conversiones sin firmar como lo hace: es fácil implementar las conversiones sobre arquitecturas que usan el complemento de dos para valores firmados.

Ahora con el programa 2:

    mov     BYTE PTR [rbp-1], 0 ; a = 0
    mov     BYTE PTR [rbp-2], 5 ; b = 5
    movzx   edx, BYTE PTR [rbp-1] ; a is promoted to 32-bit integer with value 0
    movzx   eax, BYTE PTR [rbp-2] ; b is promoted to a 32-bit integer with value 5
    mov     ecx, edx 
    sub     ecx, eax ; a - b is now done as 32-bit integers resulting in -5, which is '4294967291' when interpreted as unsigned

Vemos que A y B se promueven una vez más antes de cualquier aritmética, por lo que terminamos restando dos INTS sin firmar, lo que conduce a un UINT_MAX -4 debido al bajo flujo, que también es -5 como un valor firmado. Entonces, ya sea que lo interprete como una sustracción firmada o sin firmar, debido a que la máquina usa el formulario de complemento de dos, el resultado coincide con el estándar C sin ninguna conversión adicional.

Asignar un número negativo a una variable sin firmar es básicamente romper las reglas. Lo que estás haciendo es convertir el número negativo a un gran número positivo. Ni siquiera está garantizado, técnicamente, que la conversión es la misma de un procesador a otro: en el sistema de complemento de 1 (si aún existía), obtendría un valor diferente, por ejemplo.

Entonces obtienes lo que obtienes. No puede esperar que el álgebra firmado todavía se aplique.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top