Por que é 0 <-0x80000000?

Eu tenho abaixo de um programa simples:

#include  #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?

Ele funciona bem se eu alterar a macro para:

 #define INT32_MIN (-2147483648L) 

Alguém pode apontar o problema?

Isso é bem sutil.

Todo literal inteiro em seu programa tem um tipo. Qual tipo é regulado por uma tabela em 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 couber dentro do tipo int padrão, ele tentará o próximo tipo maior, conforme indicado na tabela acima. Então, para literais de números inteiros decimais regulares, é como:

  • Tente int
  • Se não couber, tente por long
  • Se não couber, tente por long long .

Os literais hexadecimais se comportam de maneira diferente! Se o literal não couber dentro de um tipo assinado como int , ele tentará primeiro unsigned int antes de tentar tipos maiores. Veja a diferença na tabela acima.

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

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

bal < INT32_MIN invoca as conversões aritméticas usuais e o resultado da expressão 0x80000000 é promovido de unsigned int para long long . O valor 0x80000000 é preservado e 0 é menor que 0x80000000, portanto, o resultado.

Quando você substitui o literal com 2147483648L você usa notação decimal e, portanto, o compilador não seleciona unsigned int , mas tenta encaixá-lo dentro de um long . Também o sufixo L diz que você quer um long se possível . O sufixo L realmente tem regras similares se você continuar a ler a tabela mencionada em 6.4.4.1: se o número não couber dentro do long solicitado, o que não acontece no caso de 32 bits, o compilador lhe dará um long long onde vai caber muito bem.

0x80000000 é um literal unsigned com o valor 2147483648.

Aplicando o menos unário sobre isso ainda lhe dá um tipo não assinado com um valor diferente de zero. (Na verdade, para um valor diferente de zero x , o valor que você UINT_MAX - x + 1 é UINT_MAX - x + 1 )

Este inteiro literal 0x80000000 tem o 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 esta constante inteira pode ser representada pelo tipo de unsigned int .

Então essa expressão

-0x80000000 tem o mesmo tipo unsigned int . Além disso, tem o mesmo valor 0x80000000 na representação do complemento de dois que calcula o seguinte caminho

 -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 0 com o valor não assinado 0x80000000 convertido para digitar long long int de acordo com as regras das conversões aritméticas usuais.

É evidente que 0 é menor que 0x80000000 .

A constante numérica 0x80000000 é do tipo unsigned int . Se tomarmos -0x80000000 e fizermos um elogio 2s em matemática, obtemos isto:

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

Então -0x80000000 == 0x80000000 . E comparar (0 < 0x80000000) (desde 0x80000000 é sem sinal) é verdadeiro.

Um ponto de confusão ocorre quando se pensa em - faz parte da constante numérica.

No código abaixo 0x80000000 é a constante numérica. Seu tipo é determinado apenas por isso. 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 sem enfeites são positivas.

Se for decimal, o tipo atribuído é o primeiro tipo que o manterá: int , long , long long .

Se a constante for octal ou hexadecimal, ela obtém o primeiro tipo que a mantém: int , unsigned , long , unsigned long , long long , unsigned long long .

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

-0x80000000 é também algum valor diferente de zero e sendo algum tipo não assinado, é maior que 0. Quando o código compara isso com um long long , os valores não são alterados nos dois lados da comparação, então 0 < INT32_MIN é verdadeiro.


Uma definição alternativa evita esse comportamento curioso

 #define INT32_MIN (-2147483647 - 1) 

Vamos andar em terra de fantasia por um tempo onde int e unsigned são de 48 bits.

Em seguida, 0x80000000 cabe no int e, portanto, é o tipo int . -0x80000000 é então um número negativo e o resultado da impressão é diferente.

[Voltar à palavra real]

Desde 0x80000000 cabe em algum tipo não assinado antes de um tipo assinado, como é apenas maior que some_signed_MAX ainda dentro de some_unsigned_MAX , é algum tipo não assinado.

C tem uma regra que o literal de inteiro pode ser signed ou unsigned depende se ele se encheckbox em signed ou unsigned (promoção de inteiro). Em uma máquina de 32 bits, o literal 0x80000000 será unsigned . O complemento de 2 de -0x80000000 é 0x80000000 em uma máquina de 32 bits. Portanto, o bal < INT32_MIN comparação bal < INT32_MIN está entre signed e unsigned e antes da comparação conforme a regra C, unsigned int será convertido 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, então o operando com o tipo inteiro não assinado é convertido para o tipo do operando com tipo inteiro assinado.

Portanto, bal < INT32_MIN é sempre true .

Intereting Posts