¿Por qué es 0 <-0x80000000?

253

Tengo debajo un programa simple:

#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 condición if(bal < INT32_MIN )es siempre cierta. ¿Como es posible?

Funciona bien si cambio la macro a:

#define INT32_MIN        (-2147483648L)

¿Alguien puede señalar el problema?

Jayesh Bhoi
fuente
3
¿Cuánto es CHAR_BIT * sizeof(int)?
5gon12eder
1
¿Has intentado imprimir bal?
Ryan Fitzpatrick
10
En mi humilde opinión lo más interesante es que es verdad solamente para -0x80000000, pero falsa para -0x80000000L, -2147483648y -2147483648L(gcc 4.1.2), así que la pregunta es: ¿por qué es el int literal -0x80000000diferente de lo literal int -2147483648?
Andreas Fester
2
@Bathsheba Acabo de ejecutar el programa en el compilador en línea tutorialspoint.com/codingground.htm
Jayesh Bhoi
2
Si alguna vez has notado que (algunas encarnaciones de) se <limits.h>define INT_MINcomo (-2147483647 - 1), ahora sabes por qué.
zwol

Respuestas:

363

Esto es bastante sutil.

Cada literal entero en su programa tiene un tipo. Qué tipo tiene está regulado por una tabla en 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

Si un número literal no cabe dentro del inttipo predeterminado , intentará el siguiente tipo más grande como se indica en la tabla anterior. Entonces, para los literales enteros decimales regulares es así:

  • Tratar int
  • Si no cabe, intente long
  • Si no cabe, inténtalo long long.

Sin embargo, los literales hexadecimales se comportan de manera diferente. Si el literal no cabe dentro de un tipo con signo como int, primero lo intentará unsigned intantes de pasar a probar tipos más grandes. Vea la diferencia en la tabla anterior.

Entonces, en un sistema de 32 bits, su literal 0x80000000es de tipo unsigned int.

Esto significa que puede aplicar el -operador unario en el literal sin invocar un comportamiento definido por la implementación, como lo haría al desbordar un entero con signo. En cambio, obtendrá el valor 0x80000000, un valor positivo.

bal < INT32_MINinvoca las conversiones aritméticas habituales y el resultado de la expresión 0x80000000se promueve de unsigned inta long long. El valor 0x80000000se conserva y 0 es menor que 0x80000000, de ahí el resultado.

Cuando reemplaza el literal con 2147483648L, usa la notación decimal y, por lo tanto, el compilador no elige unsigned int, sino que intenta ajustarlo dentro de a long. También el sufijo L dice que quieres un long si es posible . El sufijo L realidad tiene reglas similares si continúa para leer la tabla mencionada en 6.4.4.1: si el número no encaja dentro de la solicitada long, que no en el caso de 32 bits, el compilador le dará una long longdonde Te quedará bien.

Lundin
fuente
3
"... reemplaza el literal con -2147483648L, obtienes explícitamente un largo, que está firmado". Hmmm, En una de 32 bits longdel sistema 2147483648L, no caben en una long, lo que se convierte long long, entonces el -se aplica - o eso creía yo.
chux
2
@ASH Porque el número máximo que puede tener un int es entonces 0x7FFFFFFF. Pruébelo usted mismo:#include <limits.h> printf("%X\n", INT_MAX);
Lundin
55
@ASH No confunda la representación hexadecimal de literales enteros en el código fuente con la representación binaria subyacente de un número con signo. El literal 0x7FFFFFFFcuando se escribe en el código fuente siempre es un número positivo, pero su intvariable puede, por supuesto, contener números binarios en bruto hasta el valor 0xFFFFFFFF.
Lundin
2
@ASH ìnt n = 0x80000000fuerza una conversión del literal sin signo a un tipo con signo. Lo que sucederá depende de su compilador: es un comportamiento definido por la implementación. En este caso, eligió mostrar el literal completo en el int, sobrescribiendo el bit de signo. En otros sistemas, es posible que no sea posible representar el tipo e invocar un comportamiento indefinido; el programa puede fallar. Obtendrá el mismo comportamiento si lo hace, int n=2147483648;no está relacionado con la notación hexadecimal en absoluto.
Lundin
3
La explicación de cómo -se aplica unario a enteros sin signo podría ampliarse un poco. Siempre supuse (aunque afortunadamente nunca me basé en la suposición) que los valores sin signo se "promoverían" a valores con signo, o posiblemente que el resultado sería indefinido. (Honestamente, debería ser un error de compilación; ¿qué significa - 3uincluso?)
Kyle Strand
27

0x80000000es un unsignedliteral con valor 2147483648.

Aplicar el signo menos unario en esto todavía le da un tipo sin signo con un valor distinto de cero. (De hecho, para un valor distinto de cero x, el valor con el que termina es UINT_MAX - x + 1).

Betsabé
fuente
23

Este entero literal 0x80000000tiene tipo unsigned int.

Según el estándar C (6.4.4.1 constantes enteras)

5 El tipo de una constante entera es el primero de la lista correspondiente en la que se puede representar su valor.

Y esta constante entera se puede representar por el tipo de unsigned int.

Entonces esta expresión

-0x80000000Tiene el mismo unsigned inttipo. Además, tiene el mismo valor 0x80000000en la representación del complemento de dos que calcula de la siguiente manera

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

Esto tiene un efecto secundario si escribir por ejemplo

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

El resultado será de nuevo INT_MIN.

Así en esta condición

bal < INT32_MIN

se compara 0con el valor sin signo0x80000000 convertido a tipo long long int de acuerdo con las reglas de las conversiones aritméticas habituales.

Es evidente que 0 es menor que 0x80000000.

Vlad de Moscú
fuente
12

La constante numérica 0x80000000es de tipo unsigned int. Si tomamos -0x80000000y hacemos matemáticas complementarias de 2s, obtenemos esto:

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

Por lo tanto -0x80000000 == 0x80000000. Y comparar (0 < 0x80000000)(ya 0x80000000que no está firmado) es cierto.

dbush
fuente
Esto supone 32 bits ints. Aunque esa es una opción muy común, en cualquier implementación intpuede ser más estrecha o más amplia. Sin embargo, es un análisis correcto para ese caso.
John Bollinger
Esto no es relevante para el código de OP, -0x80000000es aritmética sin signo. ~0x800000000Es un código diferente.
MM
Esta parece ser la mejor y correcta respuesta para mí simplemente. @MM está explicando cómo tomar un complemento de dos. Esta respuesta aborda específicamente lo que el signo negativo le está haciendo al número.
Octopus
@Octopus, el signo negativo no aplica el complemento de 2 al número (!) Aunque esto parece claro, ¡no describe lo que sucede en el código -0x80000000! De hecho, el complemento de 2 es irrelevante para esta pregunta por completo.
MM
12

Se produce un punto de confusión al pensar que -es parte de la constante numérica.

En el siguiente código 0x80000000está la constante numérica. Su tipo se determina solo en eso. El -se aplica después y no cambia el tipo .

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

Las constantes numéricas sin adornos sin procesar son positivas.

Si es decimal, entonces el tipo asignado es el primer tipo que lo sostienen: int, long, long long.

Si la constante es octal o hexadecimal, se pone el primer tipo que lo sostiene: int, unsigned,long , unsigned long, long long, unsigned long long.

0x80000000, en el sistema de OP obtiene el tipo de unsigned o unsigned long. De cualquier manera, es un tipo sin signo.

-0x80000000también es un valor distinto de cero y es un tipo sin signo, es mayor que 0. Cuando el código compara eso con a long long, los valores no cambian en los 2 lados de la comparación, por lo que 0 < INT32_MINes cierto.


Una definición alternativa evita este comportamiento curioso.

#define INT32_MIN        (-2147483647 - 1)

Caminemos en tierra de fantasía por un tiempo donde int y unsignedsomos 48 bits.

Luego 0x80000000 encaja inty también lo es el tipo int. -0x80000000es entonces un número negativo y el resultado de la impresión es diferente.

[Volver a la palabra real]

Dado que 0x80000000cabe en algún tipo sin signo antes de un tipo con signo, ya que es más grande que some_signed_MAXtodavía dentro some_unsigned_MAX, es un tipo sin signo.

chux - Restablece a Monica
fuente
8

C tiene una regla de que el literal entero puede ser signedo unsigneddepende de si encaja signedo unsigned(promoción de enteros). En una 32máquina de un bit, el literal 0x80000000será unsigned. El complemento de 2 -0x80000000está 0x80000000 en una máquina de 32 bits. Por lo tanto, la comparación bal < INT32_MINes entre signedy unsignedantes de la comparación según la regla C unsigned intse convertirá long long.

C11: 6.3.1.8/1:

[...] De lo contrario, si el tipo de operando con tipo entero con signo puede representar todos los valores del tipo de operando con tipo entero sin signo, entonces el operando con tipo entero sin signo se convierte al tipo del operando con tipo entero con signo.

Por lo tanto, bal < INT32_MINes siempre true.

hacks
fuente