Digita la conversione – non firmata con firma int / char

Ho provato a eseguire il seguente programma:

#include  int main() { signed char a = -5; unsigned char b = -5; int c = -5; unsigned int d = -5; if (a == b) printf("\r\n char is SAME!!!"); else printf("\r\n char is DIFF!!!"); if (c == d) printf("\r\n int is SAME!!!"); else printf("\r\n int is DIFF!!!"); return 0; } 

Per questo programma, sto ottenendo l’output:

char is DIFF !!! int è STESSO !!!

Perché stiamo ottenendo risultati diversi per entrambi?
L’uscita dovrebbe essere come sotto?

char è STESSO !!! int è STESSO !!!

Un link per il codepad .

Ciò è dovuto alle varie regole di conversione implicite del tipo in C. Ci sono due di esse che un programmatore C deve conoscere: le solite conversioni aritmetiche e le promozioni intere (queste ultime fanno parte del primo).

Nel caso char si hanno i tipi (signed char) == (unsigned char) . Questi sono entrambi piccoli tipi di interi . Altri tipi di interi così piccoli sono bool e short . Le regole di promozione di interi dichiarano che ogni volta che un intero di tipo piccolo è un operando di un’operazione, il suo tipo verrà promosso a int , che è firmato. Ciò avverrà indipendentemente dal fatto che il tipo sia stato firmato o non firmato.

Nel caso del signed char , il segno verrà mantenuto e verrà promosso a un int contenente il valore -5. Nel caso del unsigned char , contiene un valore che è 251 (0xFB). Sarà promosso a un int contenente lo stesso valore. Finisci con

 if( (int)-5 == (int)251 ) 

Nel caso intero hai i tipi (signed int) == (unsigned int) . Non sono piccoli tipi interi, quindi le promozioni intere non si applicano. Invece, sono bilanciati dalle consuete conversioni aritmetiche , che stabiliscono che se due operandi hanno lo stesso “rank” (dimensione) ma una diversa firma, l’operando firmato viene convertito nello stesso tipo di quello senza segno. Finisci con

 if( (unsigned int)-5 == (unsigned int)-5) 

Bella domanda!

Il confronto int funziona, perché entrambi gli interi contengono esattamente gli stessi bit, quindi sono essenzialmente gli stessi. Ma per quanto riguarda il char s?

Ah, C promuove implicitamente i messaggi di posta int in varie occasioni. Questo è uno di loro. Il tuo codice dice if(a==b) , ma ciò a cui il compilatore effettivamente si rivolge è:

 if((int)a==(int)b) 

(int)a è -5, ma (int)b è 251. Questi non sono sicuramente gli stessi.

EDIT: Come indicato da @ Carbonic-Acid, (int)b è 251 solo se un char è lungo 8 bit. Se int è lungo 32 bit, (int)b è -32764.

REDIT: C’è un sacco di commenti che parlano della natura della risposta se un byte non è lungo 8 bit. L’unica differenza in questo caso è che (int)b non è 251 ma un diverso numero positivo , che non è -5. Questo non è veramente rilevante per la domanda che è ancora molto interessante.

Benvenuto nella promozione di interi . Se posso citare dal sito web:

Se un int può rappresentare tutti i valori del tipo originale, il valore viene convertito in un int; in caso contrario, viene convertito in un int unsigned. Queste sono chiamate promozioni intere. Tutti gli altri tipi sono invariati dalle promozioni intere.

C può essere davvero confuso quando fai confronti come questi, di recente ho sconcertato alcuni dei miei amici di programmazione non C con il seguente tease:

 #include  #include  int main() { char* string = "One looooooooooong string"; printf("%d\n", strlen(string)); if (strlen(string) < -1) printf("This cannot be happening :("); return 0; } 

Che in effetti stampa This cannot be happening :( e apparentemente dimostra che 25 è più piccolo di -1!

Tuttavia, ciò che accade al di sotto è che -1 è rappresentato come un numero intero senza segno che a causa della rappresentazione dei bit sottostanti è uguale a 4294967295 su un sistema a 32 bit. E naturalmente 25 è inferiore a 4294967295.

Se tuttavia eseguiamo espressamente il cast del tipo size_t restituito da strlen come numero intero con strlen :

 if ((int)(strlen(string)) < -1) 

Quindi confronterà 25 contro -1 e tutto andrà bene per il mondo.

Un buon compilatore dovrebbe avvisarti sul confronto tra un intero senza segno e un intero con segno e tuttavia è ancora così facile non vederlo (specialmente se non abiliti gli avvisi).

Ciò è particolarmente confuso per i programmatori Java poiché tutti i tipi primitivi sono firmati. Ecco cosa ha detto James Gosling (uno dei creatori di Java) sull'argomento :

Gosling: Per me come designer del linguaggio, che in questi giorni non mi considero davvero reale, quello che "semplice" ha davvero finito per significare era che potevo aspettarmi che J. Random Developer avesse le specifiche nella sua testa. Questa definizione dice che, ad esempio, Java non lo è - e in effetti molte di queste lingue finiscono con un sacco di casi d'angolo, cose che nessuno davvero capisce. Esegui un quiz su qualsiasi sviluppatore C senza firma, e molto presto scopri che quasi nessun sviluppatore C in realtà capisce cosa succede con unsigned, cosa è l'aritmetica senza segno. Cose del genere hanno reso complesso C. La parte del linguaggio di Java è, penso, piuttosto semplice. Le librerie devi cercare.

La rappresentazione esadecimale di -5 è:

  • 8-bit, complemento a complemento a complemento a due: 0xfb
  • 32-bit, complemento signed int due: 0xfffffffb

Quando converti un numero firmato in un numero senza segno, o viceversa, il compilatore fa … proprio nulla. Cosa c’è da fare? Il numero è convertibile o non lo è, nel qual caso segue un comportamento non definito o definito dall’implementazione (non ho effettivamente controllato quale) e il comportamento più efficiente definito dall’implementazione è di non fare nulla.

Quindi, la rappresentazione esadecimale di (unsigned )-5 è:

  • 0xfb unsigned char 8 bit: 0xfb
  • Int a 32 bit unsigned int : 0xfffffffb

Sembra familiare? Sono bit-a-bit come le versioni firmate.

Quando scrivi if (a == b) , dove a e b sono di tipo char , ciò che il compilatore deve effettivamente leggere è if ((int)a == (int)b) . (Questa è la “promozione intera” su cui tutti gli altri stanno sbattendo.)

Quindi, cosa succede quando convertiamo char in int ?

  • 0xfb signed char 8 bit a 32 bit con signed char signed int : 0xfb -> 0xfffffffb
    • Bene, questo ha senso perché corrisponde alle rappresentazioni di -5 sopra!
    • Si chiama “sign-extend”, perché copia il bit superiore del byte, il “sign-bit”, a sinistra nel nuovo valore più ampio.
  • unsigned char 8 bit a 32 bit signed int : 0xfb -> 0x000000fb
    • Questa volta esegue una “estensione zero” perché il tipo di origine non ha un segno , quindi non c’è alcun segno da copiare.

Quindi, a == b davvero 0xfffffffb == 0x000000fb => nessuna corrispondenza!

E, c == d davvero 0xfffffffb == 0xfffffffb => corrisponde!

Il mio punto è: non hai ricevuto un avvertimento al momento della compilazione “confrontando l’espressione firmata e non firmata”?

Il compilatore sta cercando di informarti che ha il diritto di fare cose pazzesche! 🙂 Vorrei aggiungere, cose pazzesche accadrà usando grandi valori, vicino alla capacità del tipo primitivo. E

  unsigned int d = -5; 

sta assegnando decisamente un grande valore a d, è equivalente (anche se, probabilmente non garantito per essere equivalente) essere:

  unsigned int d = UINT_MAX -4; ///Since -1 is UINT_MAX 

Modificare:

Tuttavia, è interessante notare che solo il secondo confronto dà un avvertimento (controlla il codice) . Ciò significa che il compilatore che applica le regole di conversione è sicuro che non ci saranno errori nel confronto tra unsigned char e char unsigned char (durante il confronto saranno convertiti in un tipo che può rappresentare in modo sicuro tutti i suoi possibili valori). E ha ragione su questo punto. Quindi, ti informa che questo non sarà il caso per unsigned int e int : durante il confronto uno dei 2 sarà convertito in un tipo che non può rappresentarlo completamente.

Per completezza, l’ ho verificato anche in breve : il compilatore si comporta allo stesso modo che per i caratteri e, come previsto, non ci sono errori in fase di esecuzione.

.

In relazione a questo argomento, ho recentemente posto questa domanda (ancora, orientata al C ++).