Domanda

Qui ci sono due programmi molto semplici. Mi aspetto di ottenere lo stesso risultato, ma non lo faccio. Non riesco a capire perché. Le prime uscite 251. Le seconde uscite -5. Posso capire perché la 251. Tuttavia, non vedo il motivo per cui il secondo programma mi dà un -5.

PROGRAMMA 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);

}

Output:

c hex: fb
c dec: 251

PROGRAMMA 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);

}

Output:

c hex: fffffffb
c dec: -5
È stato utile?

Soluzione

Ci sono due questioni separate qui. Il primo è il fatto che si stanno ottenendo diversi valori esadecimali per quello che sembra le stesse operazioni. Il fatto di fondo che vi manca è che chars sono promossi a ints (come lo sono shorts) per fare l'aritmetica. Ecco la differenza:

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

Qui, a è esteso a 0x00000000 e b è estesa a 0x000000fb ( non del segno esteso, perché è un unsigned char). Poi, l'aggiunta viene eseguita, e otteniamo 0x000000fb.

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

Qui, a è esteso a 0x00000000 e b è estesa a 0x00000005. Poi, la sottrazione viene eseguita, e otteniamo 0xfffffffb.

La soluzione? Bastone con chars o ints; mescolandoli può causare cose che non si aspetta.

Il secondo problema è che un unsigned int viene stampato come -5, chiaramente un valore con segno. Tuttavia, nella stringa, hai detto printf stampare il secondo argomento, interpretato come un int firmato (che significa di che cosa "%d"). Il trucco è che printf non sa cosa i tipi di variabili che si passavano in. Essa si limita interpreta loro nel modo stringa dice di. Ecco un esempio in cui diciamo printf per stampare un puntatore come un int:

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

Quando ho eseguito questo programma, ottengo un valore diverso ogni volta, che è la posizione di memoria di a, convertito in base 10. Si può notare che questo genere di cose fa sì che un avvertimento. Si consiglia di leggere tutte le avvertenze compilatore ti dà, e solo ignorarli se sei completamente che si sta facendo che cosa si intende.

Altri suggerimenti

Nel primo programma, b=-5; assegna 251 a b. (Conversioni in un tipo unsigned ridurre sempre quello valore del modulo più il valore massimo del tipo di destinazione.)

Nel secondo programma, b=5; assegna semplicemente 5 a b, esegue poi c = (a - b); la sottrazione 0-5 come tipo int a causa delle promozioni di default - In parole povere, i tipi "più piccolo di int" sono sempre promosso a int prima di essere utilizzato come operandi di operatori aritmetici e bit a bit.

Modifica Una cosa che mi mancava: Dal c ha tipo unsigned int, il risultato -5 nel secondo programma sarà convertito in unsigned int quando viene eseguito il compito di c, con conseguente UINT_MAX-4. Questo è ciò che si vede con l'identificatore %x a printf. Quando si stampa c con %d, si ottiene un comportamento indefinito, perché %d aspetta un argomento (firmato) int e passato un argomento unsigned int con un valore che non è rappresentabile in pianura (firmato) int.

Si sta utilizzando il %d di formato. Che tratta l'argomento come un numero decimale con segno (in pratica int).

Si ottiene 251 dal primo programma perché (unsigned char)-5 è 251, allora lo si stampa come una cifra decimale con segno. Esso viene promosso a 4 byte invece di 1, e quei bit sono 0, quindi gli sguardi numero come 0000...251 (dove il 251 è binario, ho appena non converto).

Si ottiene -5 dal secondo programma perché (unsigned int)-5 è qualche grande valore, ma colato a un int, è -5. Esso viene trattato come un int a causa del modo di usare printf.

Utilizzare il %ud di formato per stampare i valori decimali senza segno.

quello che stai vedendo è il risultato di come la macchina sottostante è che rappresenta i numeri come le definisce standard di C firmati a conversioni di tipo non firmati (per l'aritmetica) e numeri come la macchina di fondo è che rappresentino (per il risultato del comportamento indefinito alla fine).

Quando ho originariamente scritto la mia risposta mi aveva assunto che lo standard C non ha definito esplicitamente come valori con segno dovrebbero essere convertiti in valori senza segno, in quanto lo standard non definiscono come valori con segno dovrebbero essere rappresentati o come convertire i valori senza segno di valori con segno quando l'intervallo non rientra quello del tipo firmato .

Tuttavia, si scopre che la norma non esplicitamente definisce che durante la conversione da negativo con segno a valori senza segno positivo. Nel caso di un numero intero, un valore negativo firmata x sarà convertita UINT_MAX + 1-x, come se fosse memorizzato come un valore con segno in complemento a due e quindi interpretato come un valore senza segno.

Quindi, quando si dice:

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

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

valore di b diventa 251, perché -5 viene convertito in un tipo senza segno di valore UCHAR_MAX-5 + 1 (255-5 + 1) utilizzando lo standard C. E 'poi, dopo che la conversione che l'aggiunta avviene. Ciò rende a + b uguale a 0 + 251, che viene poi memorizzato in c. Tuttavia, quando si dice:

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

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

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

In questo caso, a e b sono promossi a unsigned int, da abbinare con c, in modo che rimangano 0 e 5 in valore. Tuttavia 0 - 5, in firmate porta matematici con numeri interi di un errore di underflow, che è definito comportare UINT_MAX + 1-5. Se questo fosse accaduto prima della promozione, il valore sarebbe UCHAR_MAX + 1-5 (vale a dire 251 di nuovo).

Tuttavia, la ragione che si vede -5 stampato nella vostra uscita è una combinazione del fatto che il numero intero senza segno UINT_MAX-4 e -5 hanno la stessa rappresentazione binaria esatta, proprio come -5 e 251 che fare con un singolo byte tipo di dati, e il fatto che quando si è utilizzato "% d", come la stringa di formattazione, che printf detto di interpretare il valore di c come un intero con segno invece di un numero intero senza segno.

Dal momento che una conversione da valori senza segno a valori con segno per i valori non validi non è definito, il risultato diventa implementazione specifica. Nel tuo caso, dal momento che la macchina sottostante usa complemento a due per valori con segno, il risultato è che il valore senza segno UINT_MAX-4 diventa il valore con segno -5.

L'unico motivo questo non avviene nel primo programma perché un unsigned int e un int firmato possono entrambi rappresentare 251, quindi la conversione tra i due è ben definito e con "% d" o "% u" no importa. Nel secondo programma, tuttavia, si traduce in un comportamento indefinito e diventa implementazione specifica dal momento che il valore della UINT_MAX-4 è andato fuori del campo di un int firmato.

Che cosa sta succedendo sotto il cofano

E 'sempre bene controllare due volte ciò che si pensa che sta accadendo o che cosa dovrebbe accadere con quello che sta realmente accadendo, quindi cerchiamo di guardare l'output in linguaggio assembly dal compilatore subito per vedere esattamente cosa sta succedendo. Ecco la parte significativa del primo programma:

    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

Si noti che, sebbene abbiamo memorizzare un valore con segno di -5 nel b byte, quando il compilatore promuove, promuove dal zero estendendo il numero, ovvero il prodotto viene interpretato come valore senza segno che 11111011 rappresenta invece del firmata valore. Poi sono aggiunti i valori promossi insieme per diventare c. Questo è anche il motivo per cui le definisce standard di C firmati alle conversioni senza segno il modo in cui lo fa -. È facile da implementare le conversioni su architetture che utilizzano complemento a due per valori con segno

Ora con il programma 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

Si vede che a e b sono ancora una volta promossi prima di ogni aritmetica, così si finisce per sottrarre due unsigned int, che porta ad un UINT_MAX-4 a causa di underflow, che è anche -5 come valore firmato. Quindi, se si interpreta come una sottrazione con o senza segno, a causaalla macchina tramite il modulo complemento a due, il risultato corrisponde allo standard C senza alcuna conversione in più.

Assegnazione di un numero negativo a una variabile senza segno è fondamentalmente rompendo le regole. Quello che stai facendo è la conversione del numero negativo per un gran numero positivo. Non sei nemmeno garantito, tecnicamente, che la conversione è la stessa da un processore all'altro -. Sul sistema del complemento a 1 di (eventuale esisteva ancora) si otterrebbe un valore diverso, ad esempio

Così si ottiene quello che si ottiene. Non si può pretendere firmato l'algebra per applicare ancora.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top