Perché è 0 <-0x80000000?


253

Di seguito ho un semplice programma:

#include <stdio.h>

#define INT32_MIN        (-0x80000000)

int main(void) 
{
    long long bal = 0;

    if(bal < INT32_MIN )
    {
        printf("Failed!!!");
    }
    else
    {
        printf("Success!!!");
    }
    return 0;
}

La condizione if(bal < INT32_MIN )è sempre vera. Come è possibile?

Funziona bene se cambio la macro in:

#define INT32_MIN        (-2147483648L)

Qualcuno può segnalare il problema?


3
Quanto costa CHAR_BIT * sizeof(int)?
5gon12eder,

1
Hai provato a stampare bal?
Ryan Fitzpatrick,

10
IMHO la cosa più interessante è che è vero solo per -0x80000000, ma falso per -0x80000000L, -2147483648e -2147483648L(gcc 4.1.2), quindi la domanda è: perché l'int letterale è -0x80000000diverso dall'int letterale -2147483648?
Andreas Fester,

2
@Bathsheba Ho appena eseguito il programma sul compilatore online tutorialspoint.com/codingground.htm
Jayesh Bhoi

2
Se hai mai notato che (alcune incarnazioni di) <limits.h>definisce INT_MINcome (-2147483647 - 1), ora sai perché.
zwol,

Risposte:


363

Questo è abbastanza sottile.

Ogni valore intero letterale nel tuo programma ha un tipo. Quale tipo ha è regolato da una tabella in 6.4.4.1:

Suffix      Decimal Constant    Octal or Hexadecimal Constant

none        int                 int
            long int            unsigned int
            long long int       long int
                                unsigned long int
                                long long int
                                unsigned long long int

Se un numero letterale non può rientrare nel inttipo predefinito , tenterà il tipo più grande successivo come indicato nella tabella sopra. Quindi, per i letterali interi decimali regolari va come:

  • Provare int
  • Se non riesce, prova long
  • Se non riesce, prova long long.

I letterali esadecimali si comportano diversamente però! Se il valore letterale non può rientrare in un tipo firmato int, tenterà unsigned intprima di passare a tipi più grandi. Vedi la differenza nella tabella sopra.

Quindi su un sistema a 32 bit, il tuo letterale 0x80000000è di tipo unsigned int.

Ciò significa che è possibile applicare l' -operatore unario sul valore letterale senza richiamare il comportamento definito dall'implementazione, come si farebbe altrimenti quando si trabocca un numero intero con segno. Invece, otterrai il valore 0x80000000, un valore positivo.

bal < INT32_MINinvoca le solite conversioni aritmetiche e il risultato dell'espressione 0x80000000viene promosso da unsigned inta long long. Il valore 0x80000000viene conservato e 0 è inferiore a 0x80000000, quindi il risultato.

Quando si sostituisce il letterale con 2147483648Lsi usa la notazione decimale e quindi il compilatore non prende unsigned int, ma piuttosto cerca di adattarlo all'interno di a long. Anche il suffisso L dice che vuoi un long se possibile . Il suffisso L ha in realtà regole simili se continui a leggere la tabella menzionata in 6.4.4.1: se il numero non rientra nella richiesta long, cosa che non accade nel caso a 32 bit, il compilatore ti fornirà un punto long longdove andrà bene.


3
"... sostituisci il letterale con -2147483648L ottieni esplicitamente un long, che è firmato." Hmmm, In a 32-bit longdel sistema 2147483648L, non si adatta in una long, in modo che diventi long long, allora la -si applica - o così ho pensato.
chux - Ripristina Monica il

2
@ASH Perché il numero massimo che un int può avere è quindi 0x7FFFFFFF. Provalo tu stesso:#include <limits.h> printf("%X\n", INT_MAX);
Lundin,

5
@ASH Non confondere la rappresentazione esadecimale dei letterali interi nel codice sorgente con la rappresentazione binaria sottostante di un numero con segno. Il valore letterale 0x7FFFFFFFquando scritto nel codice sorgente è sempre un numero positivo, ma la tua intvariabile può ovviamente contenere numeri binari non elaborati fino a un valore 0xFFFFFFFF.
Lundin,

2
@ASH ìnt n = 0x80000000impone una conversione dal letterale senza segno a un tipo con segno. Ciò che accadrà dipende dal compilatore: è un comportamento definito dall'implementazione. In questo caso ha scelto di mostrare l'intero valore letterale in int, sovrascrivendo il bit del segno. Su altri sistemi potrebbe non essere possibile rappresentare il tipo e si invoca un comportamento indefinito - il programma potrebbe bloccarsi. Avrai lo stesso comportamento se lo fai, int n=2147483648;quindi non è affatto correlato alla notazione esadecimale.
Lundin,

3
La spiegazione di come unario -viene applicato a numeri interi senza segno potrebbe essere ampliata un po '. Avevo sempre supposto (anche se fortunatamente non ho mai fatto affidamento sul presupposto) che i valori non firmati sarebbero stati "promossi" in valori firmati, o forse che il risultato non sarebbe stato definito. (Onestamente, dovrebbe essere un errore di compilazione; cosa significa - 3uanche?)
Kyle Strand

27

0x80000000è un unsignedvalore letterale con valore 2147483648.

Applicare il meno unario su questo ti dà ancora un tipo senza segno con un valore diverso da zero. (In effetti, per un valore diverso da zero x, il valore con cui si finisce è UINT_MAX - x + 1.)


23

Questo valore letterale intero 0x80000000ha tipo unsigned int.

Secondo lo standard C (6.4.4.1 Costanti intere)

5 Il tipo di una costante intera è il primo dell'elenco corrispondente in cui può essere rappresentato il suo valore.

E questa costante intera può essere rappresentata dal tipo di unsigned int.

Quindi questa espressione

-0x80000000ha lo stesso unsigned inttipo. Inoltre ha lo stesso valore 0x80000000nella rappresentazione del complemento a due che calcola il modo seguente

-0x80000000 = ~0x80000000 + 1 => 0x7FFFFFFF + 1 => 0x80000000

Questo ha un effetto collaterale se scrivere per esempio

int x = INT_MIN;
x = abs( x );

Il risultato sarà di nuovo INT_MIN.

Quindi in questa condizione

bal < INT32_MIN

viene confrontato 0con il valore senza segno0x80000000 convertito nel tipo long long int secondo le regole delle solite conversioni aritmetiche.

È evidente che 0 è inferiore a 0x80000000.


12

La costante numerica 0x80000000è di tipo unsigned int. Se prendiamo -0x80000000e facciamo matematica complimenti 2s su di esso, otteniamo questo:

~0x80000000 = 0x7FFFFFFF
0x7FFFFFFF + 1 = 0x80000000

Così -0x80000000 == 0x80000000. E il confronto (0 < 0x80000000)(poiché 0x80000000non è firmato) è vero.


Questo suppone che a 32 bit int. Sebbene sia una scelta molto comune, in ogni data implementazione intpotrebbe essere più stretta o più ampia. È un'analisi corretta per quel caso, tuttavia.
John Bollinger,

Questo non è rilevante per il codice OP, -0x80000000è aritmetica senza segno. ~0x800000000è un codice diverso.
MM

Questa sembra essere la risposta migliore e corretta per me semplicemente. @MM sta spiegando come prendere un complemento a due. Questa risposta si rivolge specificamente a ciò che il segno negativo sta facendo al numero.
Polpo,

@Octopus il segno negativo non sta applicando il complemento di 2 al numero (!) Anche se questo sembra chiaro, non sta descrivendo cosa succede nel codice -0x80000000! In effetti il ​​complemento di 2 è del tutto irrilevante per questa domanda.
MM

12

Un punto di confusione si verifica nel pensare che -sia parte della costante numerica.

Nel codice seguente 0x80000000è la costante numerica. Il suo tipo è determinato solo da quello. La -si applica in seguito e non cambia il tipo .

#define INT32_MIN        (-0x80000000)
long long bal = 0;
if (bal < INT32_MIN )

Le costanti numeriche non adornate non elaborate sono positive.

Se è decimale, quindi il tipo assegnato è primo tipo che tenerlo: int, long, long long.

Se la costante è ottale o esadecimale, ottiene il primo tipo che lo tiene: int, unsigned, long, unsigned long, long long, unsigned long long.

0x80000000, sul sistema di OP ottiene il tipo di unsignedo unsigned long. Ad ogni modo, è un tipo non firmato.

-0x80000000è anche un valore diverso da zero ed è un tipo senza segno, è maggiore di 0. Quando il codice lo confronta con a long long, i valori non vengono modificati sui 2 lati del confronto, quindi 0 < INT32_MINè vero.


Una definizione alternativa evita questo comportamento curioso

#define INT32_MIN        (-2147483647 - 1)

Camminiamo nella terra di fantasia per un po 'dove inte unsignedsono a 48 bit.

Quindi si 0x80000000adatta inte così è il tipo int. -0x80000000è quindi un numero negativo e il risultato della stampa è diverso.

[Torna alla parola reale]

Dal momento che si 0x80000000adatta a un tipo senza segno prima di un tipo con segno poiché è appena più grande che some_signed_MAXancora all'interno some_unsigned_MAX, è un tipo senza segno.


8

C ha una regola secondo cui l'intero valore letterale può essere signedo unsigneddipende dal fatto che si adatti signedo unsigned(promozione di numeri interi). Su una 32macchina a bit 0x80000000sarà il letterale unsigned. Il complemento di 2 -0x80000000è 0x80000000 su una macchina a 32 bit. Pertanto, il confronto bal < INT32_MINè tra signede unsignede prima del confronto secondo la regola C unsigned intverrà convertito in long long.

C11: 6.3.1.8/1:

[...] Altrimenti, se il tipo di operando con tipo intero con segno può rappresentare tutti i valori del tipo di operando con tipo intero senza segno, l'operando con tipo intero senza segno viene convertito nel tipo di operando con tipo intero con segno.

Pertanto, bal < INT32_MINè sempre true.

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.