c - name - meta tags seo
¿Por qué es 0<-0x80000000? (6)
C tiene una regla de que el literal entero puede estar
signed
o
unsigned
depende de si cabe en
signed
o
unsigned
(promoción de enteros).
En una máquina de
32
bits, el literal
0x80000000
estará
unsigned
.
El complemento de 2 de
-0x80000000
es
0x80000000
en una máquina de 32 bits.
Por lo tanto, la comparación
bal < INT32_MIN
es entre con
signed
y
unsigned
y antes de la comparación según la regla C
unsigned int
se convertirá en
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_MIN
es siempre
true
.
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 )
siempre es verdadera.
¿Como es posible?
Funciona bien si cambio la macro a:
#define INT32_MIN (-2147483648L)
¿Alguien puede señalar el problema?
Este entero literal
0x80000000
tiene el 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
-0x80000000
tiene el mismo tipo
unsigned int
.
Además, tiene el mismo valor
0x80000000
en 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á nuevamente
INT_MIN
.
Así en esta condición
bal < INT32_MIN
se compara
0
con el valor
sin signo
0x80000000
convertido a tipo long long int según las reglas de las conversiones aritméticas habituales.
Es evidente que 0 es menor que
0x80000000
.
Esto es bastante sutil.
Cada literal entero en su programa tiene un tipo. El tipo que 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 tipo
int
predeterminado, intentará el siguiente tipo más grande como se indica en la tabla anterior.
Entonces, para los literales enteros decimales regulares es así:
-
Prueba
int
-
Si no puede caber, intente
long
-
Si no cabe, intente
long long
.
Sin embargo, los literales hexadecimales se comportan de manera diferente.
Si el literal no puede caber dentro de un tipo con signo como
int
, primero intentará
unsigned int
antes de pasar a probar tipos más grandes.
Vea la diferencia en la tabla anterior.
Entonces, en un sistema de 32 bits, su literal
0x80000000
es 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_MIN
invoca las conversiones aritméticas habituales y el resultado de la expresión
0x80000000
se promueve de
unsigned int
a
long long
.
El valor
0x80000000
se 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 un
long
.
También el sufijo L dice que quieres un
long
si es posible
.
El sufijo L en realidad tiene reglas similares si continúa leyendo la tabla mencionada en 6.4.4.1: si el número no cabe dentro del
long
solicitado, lo que no sucede en el caso de 32 bits, el compilador le dará un
long long
donde encajará bien.
La constante numérica
0x80000000
es de tipo
unsigned int
.
Si tomamos
-0x80000000
y hacemos 2s de matemática complementaria, obtenemos esto:
~0x80000000 = 0x7FFFFFFF
0x7FFFFFFF + 1 = 0x80000000
Entonces
-0x80000000 == 0x80000000
.
Y la comparación
(0 < 0x80000000)
(ya que
0x80000000
no está firmado) es verdadera.
Un punto de confusión ocurre al pensar que
-
es parte de la constante numérica.
En el siguiente código
0x80000000
es 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 contendrá:
int
,
long
,
long long
.
Si la constante es octal o hexadecimal, obtiene el primer tipo que la contiene:
int
,
unsigned
,
long
,
unsigned long
,
long long
,
unsigned long long
.
0x80000000
, en el sistema de OP obtiene el tipo de
unsigned long
unsigned
o
unsigned long
.
De cualquier manera, es un tipo sin signo.
-0x80000000
también es un valor distinto de cero y es un tipo sin signo, es mayor que 0. Cuando el código compara eso con un
long long
, los
valores
no cambian en los 2 lados de la comparación, por lo que
0 < INT32_MIN
es verdadero.
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
unsigned
son de 48 bits.
Entonces
0x80000000
encaja en
int
y también lo es el tipo
int
.
-0x80000000
es un número negativo y el resultado de la impresión es diferente.
[Volver a la palabra real]
Dado que
0x80000000
cabe en algún tipo sin signo antes de un tipo con signo, ya que es más grande que
some_signed_MAX
pero dentro de
some_unsigned_MAX
, es un tipo sin signo.
0x80000000
es un literal
unsigned
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
).