Confronti firmati / non firmati


87

Sto cercando di capire perché il seguente codice non emette un avviso nel punto indicato.

//from limits.h
#define UINT_MAX 0xffffffff /* maximum unsigned int value */
#define INT_MAX  2147483647 /* maximum (signed) int value */
            /* = 0x7fffffff */

int a = INT_MAX;
//_int64 a = INT_MAX; // makes all warnings go away
unsigned int b = UINT_MAX;
bool c = false;

if(a < b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a > b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a <= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a >= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a == b) // no warning <--- warning expected here
    c = true;
if(((unsigned int)a) == b) // no warning (as expected)
    c = true;
if(a == ((int)b)) // no warning (as expected)
    c = true;

Pensavo avesse a che fare con la promozione in background, ma gli ultimi due sembrano dire il contrario.

A mio avviso, il primo ==confronto è una mancata corrispondenza con segno / non firmato come gli altri?


3
gcc 4.4.2 stampa un avviso quando viene richiamato con '-Wall'
bobah

Questa è una speculazione ma forse sta ottimizzando tutti i confronti poiché conosce la risposta al momento della compilazione.
Null Set

2
Ah! ri. Il commento di bobah: ho attivato tutti gli avvisi e ora viene visualizzato l'avviso mancante. Sono dell'opinione che avrebbe dovuto apparire con la stessa impostazione del livello di avviso degli altri confronti.
Peter

1
@bobah: odio davvero che gcc 4.4.2 stampi quell'avvertimento (senza alcun modo per dirgli di stamparlo solo per la disuguaglianza), poiché tutti i modi per silenziare quell'avviso peggiorano le cose . La promozione predefinita converte in modo affidabile sia -1 che ~ 0 nel valore più alto possibile di qualsiasi tipo non firmato, ma se silenzia l'avviso lanciandolo tu stesso, devi conoscere il tipo esatto . Quindi, se cambi il tipo (estendilo a unsigned long long), i tuoi confronti con bare -1funzioneranno ancora (ma quelli danno un avvertimento) mentre i tuoi confronti con -1uo (unsigned)-1falliranno entrambi miseramente.
Jan Hudec

Non so perché hai bisogno di un avvertimento e perché i compilatori non riescono a farlo funzionare. -1 è negativo, quindi è inferiore a qualsiasi numero senza segno. Semplici.
CashCow

Risposte:


96

Quando si confronta firmato con non firmato, il compilatore converte il valore firmato in unsigned. Per l'uguaglianza, questo non importa, -1 == (unsigned) -1. Per altri confronti è importante, ad esempio, il seguente è vera: -1 > 2U.

EDIT: Riferimenti:

5/9: (Espressioni)

Molti operatori binari che prevedono operandi di tipo aritmetico o di enumerazione causano conversioni e producono tipi di risultati in modo simile. Lo scopo è produrre un tipo comune, che è anche il tipo del risultato. Questo modello è chiamato le solite conversioni aritmetiche, che sono definite come segue:

  • Se uno degli operandi è di tipo long double, l'altro deve essere convertito in long double.

  • Altrimenti, se uno degli operandi è double, l'altro deve essere convertito in double.

  • Altrimenti, se uno degli operandi è float, l'altro deve essere convertito in float.

  • In caso contrario, le promozioni integrali (4.5) devono essere eseguite su entrambi gli operandi.54)

  • Quindi, se uno degli operandi è lungo senza segno, l'altro deve essere convertito in lungo senza segno.

  • Altrimenti, se un operando è un long int e l'altro unsigned int, allora se un long int può rappresentare tutti i valori di un unsigned int, il unsigned int deve essere convertito in un long int; altrimenti entrambi gli operandi devono essere convertiti in unsigned long int.

  • Altrimenti, se uno degli operandi è lungo, l'altro deve essere convertito in lungo.

  • Altrimenti, se uno degli operandi è senza segno, l'altro deve essere convertito in non firmato.

4.7 / 2: (Conversioni integrali)

Se il tipo di destinazione è senza segno, il valore risultante è il minimo intero senza segno congruente all'intero di origine (modulo 2 n dove n è il numero di bit utilizzati per rappresentare il tipo senza segno). [Nota: in una rappresentazione in complemento a due, questa conversione è concettuale e non vi è alcun cambiamento nello schema di bit (se non c'è troncamento). ]

EDIT2: livelli di avviso MSVC

Ciò di cui viene avvertito nei diversi livelli di avviso di MSVC sono, ovviamente, le scelte fatte dagli sviluppatori. Per come la vedo io, le loro scelte in relazione all'uguaglianza firmato / non firmato rispetto a confronti maggiori / minori hanno senso, questo è del tutto soggettivo ovviamente:

-1 == -1significa lo stesso di -1 == (unsigned) -1- lo trovo un risultato intuitivo.

-1 < 2 non significa lo stesso di -1 < (unsigned) 2- Questo è meno intuitivo a prima vista e IMO merita un avvertimento "anticipato".


Come puoi convertire firmato in non firmato? Qual è la versione non firmata del valore con segno -1? (con segno -1 = 1111, mentre senza segno 15 = 1111, bit per bit possono essere uguali, ma non sono logicamente uguali.) Capisco che se forzi questa conversione funzionerà, ma perché il compilatore dovrebbe farlo? È illogico. Inoltre, come ho commentato sopra, quando ho alzato gli avvisi è apparso l'avviso == mancante, che sembra confermare quello che dico?
Peter

1
Come dice 4.7 / 2, da segno a non segno significa nessun cambiamento nel modello di bit per il complemento a due. Quanto al motivo per cui il compilatore lo fa, è richiesto dallo standard C ++. Credo che il ragionamento alla base degli avvertimenti di VS a diversi livelli sia la possibilità che un'espressione non sia intenzionale - e sarei d'accordo con loro sul fatto che il confronto di uguaglianza di firmato / non firmato è "meno probabile" di essere un problema rispetto ai confronti di disuguaglianza. Questo è ovviamente soggettivo: si tratta di scelte fatte dagli sviluppatori del compilatore VC.
Erik

Ok, penso di averlo quasi capito. Il modo in cui lo leggo è che il compilatore sta (concettualmente) facendo: 'if (((unsigned _int64) 0x7fffffff) == ((unsigned _int64) 0xffffffff))', perché _int64 è il tipo più piccolo che può rappresentare sia 0x7fffffff che 0xffffffff in termini non firmati?
Peter

2
In realtà il confronto con (unsigned)-1o -1uè spesso peggio del confronto con -1. Questo perché (unsigned __int64)-1 == -1, ma (unsigned __int64)-1 != (unsigned)-1. Quindi, se il compilatore fornisce un avviso, si tenta di silenziarlo eseguendo il cast su unsigned o utilizzando -1ue se il valore è effettivamente 64 bit o se lo si cambia in un secondo momento, si romperà il codice! E ricorda che size_tè senza segno, a 64 bit solo su piattaforme a 64 bit e l'utilizzo di -1 come valore non valido è molto comune con esso.
Jan Hudec

1
Forse allora i cpmpiler non dovrebbero farlo. Se confronta firmato e non firmato, controlla se il valore con segno è negativo. In tal caso, è garantito che sia inferiore a quello non firmato a prescindere.
CashCow

33

Perché gli avvisi firmati / non firmati sono importanti e i programmatori devono prestare loro attenzione, è dimostrato dal seguente esempio.

Indovina l'output di questo codice?

#include <iostream>

int main() {
        int i = -1;
        unsigned int j = 1;
        if ( i < j ) 
            std::cout << " i is less than j";
        else
            std::cout << " i is greater than j";

        return 0;
}

Produzione:

i is greater than j

Sorpreso? Demo in linea: http://www.ideone.com/5iCxY

Bottomline: in confronto, se un operando è unsigned, allora l'altro operando viene convertito implicitamente unsigned se il suo tipo è firmato!


2
Ha ragione! È stupido, ma ha ragione. Questo è un grosso problema che non ho mai incontrato prima. Perché non converte il valore senza segno in un valore con segno (più grande) ?! Se fai "if (i <((int) j))" funziona come ti aspetteresti. Sebbene "if (i <((_int64) j))" avrebbe più senso (supponendo, cosa che non puoi, che _int64 sia il doppio di int).
Peter

6
@Peter "Perché non converte il unsgiend in un valore con segno (più grande)?" La risposta è semplice: potrebbe non esserci un valore con segno più grande. Su una macchina a 32 bit, nei giorni in cui non molto long, sia int che long erano a 32 bit, e non c'era niente di più grande. Quando si confrontano firmato e non firmato, i primi compilatori C ++ hanno convertito entrambi in firmato. Poiché non ricordo quali ragioni, il comitato per gli standard C ha cambiato questo. La tua migliore soluzione è evitare il più possibile i non firmati.
James Kanze

5
@JamesKanze: sospetto che abbia a che fare anche con il fatto che il risultato di un overflow con segno è un comportamento indefinito mentre il risultato di un overflow senza segno non lo è e quindi la conversione del valore con segno negativo in non firmato è definita mentre la conversione di un valore grande senza segno in segno negativo il valore non lo è .
Jan Hudec

2
@ James Il compilatore potrebbe sempre generare un assembly che implementerebbe la semantica più intuitiva di questo confronto senza eseguire il casting su un tipo più grande. In questo particolare esempio, sarebbe sufficiente verificare prima se i<0. Quindi iè più piccolo di jsicuro. Se inon è minore di zero, ìpuò essere convertito in sicurezza in non firmato per confrontarlo j. Certo, i confronti tra firmato e non firmato sarebbero più lenti, ma il loro risultato sarebbe in un certo senso più corretto.
Sven

@ Anche io sono d'accordo. Lo standard avrebbe potuto richiedere che i confronti funzionassero per tutti i valori effettivi, invece di convertirli in uno dei due tipi. Tuttavia, ciò funzionerebbe solo per i confronti; Sospetto che il comitato non volesse regole diverse per i confronti e altre operazioni (e non volesse affrontare il problema di specificare i confronti quando il tipo effettivamente confrontato non esisteva).
James Kanze

4

L'operatore == fa solo un confronto bit per bit (con una semplice divisione per vedere se è 0).

Il più piccolo / più grande dei confronti si basa molto di più sul segno del numero.

Esempio a 4 bit:

1111 = 15? o -1?

quindi se hai 1111 <0001 ... è ambiguo ...

ma se hai 1111 == 1111 ... È la stessa cosa anche se non volevi che fosse.


Lo capisco, ma non risponde alla mia domanda. Come fai notare, 1111! = 1111 se i segni non corrispondono. Il compilatore sa che c'è una mancata corrispondenza tra i tipi, quindi perché non lo avvisa? (Il punto è che il mio codice potrebbe contenere molte di queste mancate corrispondenze di cui non sono stato avvisato.)
Peter

È il modo in cui è progettato. Il test di uguaglianza verifica la somiglianza. Ed è simile. Sono d'accordo con te sul fatto che non dovrebbe essere così. Potresti fare una macro o qualcosa che sovraccarica x == y to be! ((X <y) || (x> y))
Yochai Timmer

1

In un sistema che rappresenta i valori utilizzando 2 complementi (i processori più moderni) sono uguali anche nella loro forma binaria. Questo potrebbe essere il motivo per cui il compilatore non si lamenta di a == b .

E per me è strano che il compilatore non ti avverta su a == ((int) b) . Penso che dovrebbe darti un avviso di troncamento intero o qualcosa del genere.


1
La filosofia di C / C ++ è: il compilatore si fida che lo sviluppatore sappia cosa sta facendo quando converte esplicitamente tra tipi. Quindi, nessun avviso (almeno per impostazione predefinita - credo che ci siano compilatori che generano avvisi per questo se il livello di avviso è impostato su un valore superiore a quello predefinito).
Péter Török

0

La riga di codice in questione non genera un avviso C4018 perché Microsoft ha utilizzato un numero di avviso diverso (ad esempio C4389 ) per gestire tale caso e C4389 non è abilitato per impostazione predefinita (ad esempio a livello 3).

Da Microsoft docs per C4389:

// C4389.cpp
// compile with: /W4
#pragma warning(default: 4389)

int main()
{
   int a = 9;
   unsigned int b = 10;
   if (a == b)   // C4389
      return 0;
   else
      return 0;
};

Le altre risposte hanno spiegato abbastanza bene perché Microsoft potrebbe aver deciso di creare un caso speciale con l'operatore di uguaglianza, ma trovo che quelle risposte non siano molto utili senza menzionare C4389 o come abilitarlo in Visual Studio .

Dovrei anche menzionare che se hai intenzione di abilitare C4389, potresti anche considerare di abilitare C4388. Sfortunatamente non esiste una documentazione ufficiale per C4388 ma sembra apparire in espressioni come le seguenti:

int a = 9;
unsigned int b = 10;
bool equal = (a == b); // C4388
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.