c - float - unsigned long
¿Por qué el int más pequeño, −2147483648, tiene el tipo ''long''? (2)
Esta pregunta ya tiene una respuesta aquí:
- ¿Por qué es 0 <-0x80000000? 6 respuestas
- (-2147483648> 0) devuelve verdadero en C ++? 4 respuestas
Para un proyecto escolar, tengo que codificar la función C printf. Las cosas van bastante bien, pero hay una pregunta para la que no puedo encontrar una buena respuesta, así que aquí estoy.
printf("PRINTF(d) /t: %d/n", -2147483648);
me dice (
gcc -Werror -Wextra -Wall
):
error: format specifies type ''int'' but the argument has type ''long''
[-Werror,-Wformat]
printf("PRINTF(d) /t: %d/n", -2147483648);
~~ ^~~~~~~~~~~
%ld
Pero si uso una variable int, todo va bien:
int i;
i = -2147483648;
printf("%d", i);
¿Por qué?
EDITAR:
Entendí muchos puntos, y fueron muy interesantes.
De todos modos, supongo que
printf
está usando
<stdarg.h>
librairy y, por lo tanto,
va_arg(va_list ap, type)
también debería devolver el tipo correcto.
Para
%d
y
%i
, obviamente el tipo devuelto es un
int
.
¿Cambia algo?
El problema es que
-2147483648
no es un entero literal.
Es una expresión que consiste en el operador de negación unario
-
y el número entero
2147483648
, que es demasiado grande para ser un
int
si
int
s son 32 bits.
Como el compilador elegirá un número entero con el tamaño adecuado para representar
2147483648
antes de aplicar el operador de negación, el tipo de resultado será mayor que un
int
.
Si sabe que sus
int
s son de 32 bits y desea evitar la advertencia sin mutilar la legibilidad, use un reparto explícito:
printf("PRINTF(d) /t: %d/n", (int)(-2147483648));
Ese es el comportamiento definido en una máquina de complemento de 2 con
int
s de 32 bits.
Para una mayor portabilidad teórica, use
INT_MIN
lugar del número y díganos dónde encontró una máquina que no sea un complemento de 2 para probarla.
Para ser claros, ese último párrafo fue en parte una broma.
INT_MIN
es definitivamente el camino a seguir si te refieres a "el
int
más pequeño", porque
int
varía en tamaño.
Todavía hay muchas implementaciones de 16 bits, por ejemplo.
Escribir -2
31
solo es útil si definitivamente siempre quiere decir con precisión ese valor, en cuyo caso probablemente usaría un tipo de tamaño fijo como
int32_t
lugar de
int
.
Es posible que desee alguna alternativa a escribir el número en decimal para que sea más claro para aquellos que no noten la diferencia entre
2147483648
y
2174483648
, pero deben tener cuidado.
Como se mencionó anteriormente, en una máquina de 32 bits con 2 complementos,
(int)(-2147483648)
no se desbordará y, por lo tanto, está bien definido, porque
-2147483648
tratará como un tipo con signo más amplio.
Sin embargo, lo mismo no es cierto para
(int)(-0x80000000)
.
0x80000000
se tratará como un
unsigned int
(ya que se ajusta a la representación sin signo);
-0x80000000
está bien definido (pero el
-
no tiene efecto si
int
es de 32 bits), y la conversión del
unsigned int
0x80000000
unsigned int
resultante a
int
implica un desbordamiento.
Para evitar el desbordamiento, necesitaría convertir la constante hexadecimal a un tipo con signo:
(int)(-(long long)(0x80000000))
.
Del mismo modo, debe tener cuidado si desea utilizar el operador de desplazamiento a la izquierda.
1<<31
es un comportamiento indefinido en máquinas de 32 bits con
int
s de 32 bits (o más pequeños);
solo se evaluará a 2
31
si
int
tiene al menos 33 bits, porque el desplazamiento a la izquierda por
k
bits solo está bien definido si
k
es estrictamente menor que el número de bits sin signo del tipo entero del argumento de la izquierda.
1LL<<31
es seguro, ya que se requiere
long long int
para poder representar 2
63
-1, por lo que su tamaño de bits debe ser mayor que 32. Entonces, la forma
(int)(-(1LL<<31))
Es posiblemente el más legible. YMMV.
Para cualquier pedante que pase, esta pregunta está etiquetada con C, y el último borrador de C (n1570.pdf) dice, con respecto a
E1 << E2
, donde
E1
tiene un tipo con signo, que el valor se define solo si
E1
no es negativo y
E1 × 2
E2
"es representable en el tipo de resultado".
(§6.5.7 para 4).
Eso es diferente de C ++, en el que la aplicación del operador de desplazamiento a la izquierda se define si
E1
no es negativo y
E1 × 2
E2
"es representable
en el tipo correspondiente sin signo del tipo
de resultado" (§5.8 párr. 2, énfasis agregado).
En C ++, de acuerdo con el borrador del estándar más reciente, la conversión de un valor entero a un tipo entero con signo se define en la implementación si el valor no se puede representar en el tipo de destino (§4.7 párr. 3). El párrafo correspondiente de la norma C - §6.3.1.3 párr. 3 - dice que "o bien el resultado está definido por la implementación o se genera una señal definida por la implementación".)
En C,
-2147483648
no es una constante entera.
2147483648
es una constante entera, y
-
es solo un operador unario aplicado a ella, produciendo una expresión constante.
El valor de
2147483648
no cabe en un
int
(es demasiado grande,
2147483647
suele ser el número entero más grande) y, por lo tanto, la constante entera tiene un tipo
long
, lo que causa el problema que observa.
Si desea mencionar el límite inferior para un
int
, use la macro
INT_MIN
de
<limits.h>
(el enfoque portátil) o evite mencionar
2147483648
cuidado:
printf("PRINTF(d) /t: %d/n", -1 - 2147483647);