Por que 0 <-0x80000000?

253

Eu tenho abaixo um programa simples:

#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;
}

A condição if(bal < INT32_MIN )é sempre verdadeira. Como isso é possível?

Funciona bem se eu alterar a macro para:

#define INT32_MIN        (-2147483648L)

Alguém pode apontar o problema?

Jayesh Bhoi
fonte
3
Quanto custa CHAR_BIT * sizeof(int)?
precisa saber é o seguinte
1
Você já tentou imprimir bal?
Ryan Fitzpatrick
10
IMHO a coisa mais interessante é que é verdade única para -0x80000000, mas falsa para -0x80000000L, -2147483648e -2147483648L(gcc 4.1.2), então a questão é: porque é que o int literal -0x80000000diferente do literal int -2147483648?
Andreas Fester
2
@Bathsheba eu apenas correr programa no compilador linha tutorialspoint.com/codingground.htm
Jayesh Bhoi
2
Se você já reparou que (algumas encarnações de) <limits.h>define INT_MINcomo (-2147483647 - 1), agora você sabe o porquê.
Zwol

Respostas:

363

Isso é bastante sutil.

Todo literal inteiro no seu programa tem um tipo. Que tipo possui é regulado por uma tabela no 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 um número literal não puder caber dentro do inttipo padrão , ele tentará o próximo tipo maior, conforme indicado na tabela acima. Portanto, para literais inteiros decimais regulares, é assim:

  • Experimentar int
  • Se não couber, tente long
  • Se não couber, tente long long.

Literais hexadecimais se comportam de maneira diferente! Se o literal não puder caber em um tipo assinado int, ele tentará primeiro unsigned intantes de tentar tipos maiores. Veja a diferença na tabela acima.

Portanto, em um sistema de 32 bits, seu literal 0x80000000é do tipo unsigned int.

Isso significa que você pode aplicar o -operador unário no literal sem chamar o comportamento definido pela implementação, como faria ao estourar um número inteiro assinado. Em vez disso, você receberá o valor 0x80000000, um valor positivo.

bal < INT32_MINchama as conversões aritméticas usuais e o resultado da expressão 0x80000000é promovido de unsigned intpara long long. O valor 0x80000000é preservado e 0 é menor que 0x80000000, daí o resultado.

Quando você substitui o literal, 2147483648Lusa a notação decimal e, portanto, o compilador não escolhe unsigned int, mas tenta ajustá-lo dentro de a long. Além disso, o sufixo L indica que você deseja um, long se possível . O sufixo L realmente possui regras semelhantes se você continuar lendo a tabela mencionada no 6.4.4.1: se o número não couber dentro do solicitado long, o que não acontece no caso de 32 bits, o compilador lhe dará um local long longonde vai caber muito bem.

Lundin
fonte
3
"... substitua o literal por -2147483648L, você recebe explicitamente um longo, que é assinado." Hmmm, em um longsistema de 32 bits 2147483648L, não cabe em um long, então ele se torna long long, então o -é aplicado - ou assim eu pensava.
chux - Restabelece Monica
2
@ASH Porque o número máximo que um int pode ter é então 0x7FFFFFFF. Tente você mesmo:#include <limits.h> printf("%X\n", INT_MAX);
Lundin
5
@ASH Não confunda a representação hexadecimal de literais inteiros no código-fonte com a representação binária subjacente de um número assinado. O literal 0x7FFFFFFFquando escrito no código fonte é sempre um número positivo, mas inté claro que sua variável pode conter números binários brutos até o valor 0xFFFFFFFF.
Lundin
2
@ASH ìnt n = 0x80000000força uma conversão do literal não assinado para um tipo assinado. O que acontecerá depende do seu compilador - é um comportamento definido pela implementação. Nesse caso, ele escolheu mostrar todo o literal no int, substituindo o bit do sinal. Em outros sistemas, pode não ser possível representar o tipo e você invoca um comportamento indefinido - o programa pode falhar. Você obterá o mesmo comportamento, se o fizer int n=2147483648;, não está relacionado à notação hexadecimal.
Lundin
3
A explicação de como o unário -é aplicado a números inteiros não assinados pode ser expandido um pouco. Eu sempre assumi (embora felizmente nunca confiei na suposição) que valores não assinados seriam "promovidos" a valores assinados, ou possivelmente que o resultado seria indefinido. (Honestamente, ele deve ser um erro de compilação, o que é que - 3uisso quer dizer?)
Kyle Strand
27

0x80000000é um unsignedliteral com o valor 2147483648.

A aplicação do menos unário nisso ainda fornece um tipo não assinado com um valor diferente de zero. (De fato, para um valor diferente de zero x, o valor com o qual você termina é UINT_MAX - x + 1).

Bathsheba
fonte
23

Este literal inteiro 0x80000000tem tipo unsigned int.

De acordo com o padrão C (6.4.4.1 Constantes inteiras)

5 O tipo de uma constante inteira é o primeiro da lista correspondente na qual seu valor pode ser representado.

E essa constante inteira pode ser representada pelo tipo de unsigned int.

Então essa expressão

-0x80000000tem o mesmo unsigned inttipo. Além disso, tem o mesmo valor 0x80000000na representação do complemento dos dois que calcula da seguinte maneira

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

Isso tem um efeito colateral se escrever, por exemplo

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

O resultado será novamente INT_MIN.

Assim, nesta condição

bal < INT32_MIN

é comparado 0com o valor não assinado0x80000000 convertido para o tipo long long int, de acordo com as regras das conversões aritméticas comuns.

É evidente que 0 é menor que 0x80000000.

Vlad de Moscou
fonte
12

A constante numérica 0x80000000é do tipo unsigned int. Se fizermos -0x80000000e fizermos 2s como complemento de matemática, obtemos o seguinte:

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

Então -0x80000000 == 0x80000000. E comparar (0 < 0x80000000)(já que 0x80000000não está assinado) é verdadeiro.

dbush
fonte
Isso supõe ints de 32 bits . Embora essa seja uma escolha muito comum, em qualquer implementação específica intpode ser mais estreita ou mais ampla. É uma análise correta para esse caso, no entanto.
John Bollinger
Isso não é relevante para o código do OP, -0x80000000é uma aritmética sem sinal. ~0x800000000é um código diferente.
MM
Esta parece ser a melhor e correta resposta para mim, basta colocar. @MM ele está explicando como fazer um complemento de dois. Esta resposta aborda especificamente o que o sinal negativo está fazendo com o número.
Octopus
@ Octopus, o sinal negativo não está aplicando o complemento de 2 no número (!) Embora isso pareça claro, não está descrevendo o que acontece no código -0x80000000! De fato, o complemento de 2 é totalmente irrelevante para esta questão.
MM
12

Um ponto de confusão ocorre ao pensar que -é parte da constante numérica.

No código abaixo 0x80000000está a constante numérica. Seu tipo é determinado apenas nisso. O -é aplicado posteriormente e não altera o tipo .

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

As constantes numéricas não adornadas não processadas são positivas.

Se for decimal, então o tipo atribuído é o primeiro tipo que vai prendê-lo: int, long, long long.

Se a constante é octal ou hexadecimal, torna-se o primeiro tipo que mantém: int, unsigned, long, unsigned long, long long, unsigned long long.

0x80000000, no sistema do OP obtém o tipo de unsignedou unsigned long. De qualquer forma, é algum tipo não assinado.

-0x80000000também é um valor diferente de zero e, sendo um tipo não assinado, é maior que 0. Quando o código compara isso a long long, os valores não são alterados nos dois lados da comparação, assim 0 < INT32_MINé verdade.


Uma definição alternativa evita esse comportamento curioso

#define INT32_MIN        (-2147483647 - 1)

Vamos caminhar na terra da fantasia por um tempo, onde inte unsignedsão de 48 bits.

Então 0x80000000se encaixa inte assim é o tipo int. -0x80000000é então um número negativo e o resultado da impressão é diferente.

[Voltar à palavra real]

Como 0x80000000se encaixa em algum tipo não assinado antes de um tipo assinado, pois é um pouco maior do que some_signed_MAXainda dentro some_unsigned_MAX, é um tipo não assinado.

chux - Restabelecer Monica
fonte
8

C tem uma regra de que o literal inteiro pode ser signedou unsigneddepende de se encaixar signedou unsigned(promoção de número inteiro). Em uma 32máquina de bits, o literal 0x80000000será unsigned. O complemento do 2 -0x80000000está 0x80000000 em uma máquina de 32 bits. Portanto, a comparação bal < INT32_MINé entre signede unsignedantes da comparação, conforme a regra C unsigned intserá convertida em long long.

C11: 6.3.1.8/1:

[...] Caso contrário, se o tipo do operando com o tipo inteiro assinado puder representar todos os valores do tipo do operando com o tipo inteiro não assinado, o operando com o tipo inteiro não assinado será convertido no tipo do operando com tipo inteiro assinado.

Portanto, bal < INT32_MINé sempre true.

haccks
fonte