¿Por qué el int más pequeño, −2147483648, tiene el tipo ‘largo’? [duplicate]

7 minutos de lectura

avatar de usuario
Arthur Thévenot

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 printf está usando el <stdarg.h> biblioteca y así, va_arg(va_list ap, type) también debe devolver el tipo correcto. Para %d y %iobviamente el tipo devuelto es un int. ¿Cambia algo?

  • A su pregunta adicional que ya respondí, pero luego se eliminó mi comentario relacionado: va_arg() no sabe qué tipo tiene el argumento que intenta obtener. Debe saber eso y si intenta obtener un tipo diferente al que se pasó como argumento, ese es un comportamiento indefinido. Esto también se aplica si lo hace printf("%d\n", -2147483648) como el argumento tiene tipo long pero printf trata de buscar un int.

    – fuz

    19 de enero de 2016 a las 7:40

  • No es un duplicado. Si lee la respuesta aceptada en la otra pregunta, se debe a un comportamiento indefinido específico del contexto de la pregunta. Esta pregunta no se trata de un comportamiento indefinido. Esta pregunta es sobre C y la otra es sobre C++. Ambos idiomas tienen reglas similares para la promoción, pero puede haber diferencias sutiles. Esta pregunta ayudará a más visitantes futuros, y tal vez ya lo haya hecho si el conteo de votos mucho más alto es una indicación.

    – Adrián McCarthy

    1 de marzo de 2016 a las 19:32

  • Tampoco es un duplicado de la segunda pregunta. El hecho clave allí es que especificaron el literal en hexadecimal, lo que significa que es un int sin firmar en lugar de un largo firmado.

    – Adrián McCarthy

    1 de marzo de 2016 a las 19:34

  • @AdrianMcCarthy especificaron el literal en hexadecimal, lo que significa que está sin firmar en lugar de estar firmado por mucho tiempo. Eso se puede leer como “las constantes hexadecimales siempre están sin firmar”. Los antiguos compiladores de C anteriores al estándar a menudo hacían que las constantes hexadecimales no estuvieran firmadas, por lo que eso podría ser confuso. Por 6.4.4.1 Constantes enteraspárrafo 5: “El tipo de una constante entera es el primero de la lista correspondiente en la que se puede representar su valor”. En este caso, 0x80000000 es un unsigned int aquí porque encaja en un unsigned int pero es demasiado grande para [signed] int.

    –Andrew Henle

    17 de julio de 2018 a las 19:48


  • @AdrianMcCarthy Reescribí mi comentario para dejar en claro que solo estaba tratando de aclarar y eliminé mi comentario anterior

    –Andrew Henle

    17 de julio de 2018 a las 19:49

avatar de usuario
fuz

C ª, -2147483648 no es una constante entera. 2147483648 es una constante entera, y - es solo un operador unario aplicado a él, produciendo una expresión constante. El valor de 2147483648 no cabe en un int (es uno demasiado grande, 2147483647 es típicamente el entero más grande) y por lo tanto la constante entera tiene tipo long, que causa el problema que observa. Si desea mencionar el límite inferior para un intusa la macro INT_MIN desde <limits.h> (el enfoque portátil) o evitar cuidadosamente mencionar 2147483648:

printf("PRINTF(d) \t: %d\n", -1 - 2147483647);

avatar de usuario
rico

El problema es ese -2147483648 no es un literal entero. Es una expresión que consiste en el operador de negación unario - y el entero 2147483648que es demasiado grande para ser un int si intson de 32 bits. Dado que el compilador elegirá un entero con signo de tamaño apropiado para representar 2147483648 antes de aplicar el operador de negación, el tipo del resultado será mayor que un int.

Si sabes que tu ints son de 32 bits y desea evitar la advertencia sin mutilar la legibilidad, use un molde explícito:

printf("PRINTF(d) \t: %d\n", (int)(-2147483648));

Ese es el comportamiento definido en una máquina de complemento a 2 con 32 bits ints.

Para una mayor portabilidad teórica, utilice INT_MIN en lugar del número, e infórmenos dónde encontró una máquina que no sea complemento a 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 más pequeño int“, porque int varía en tamaño. Todavía hay muchas implementaciones de 16 bits, por ejemplo. Escribir -231 solo es útil si definitivamente siempre quiere decir precisamente ese valor, en cuyo caso probablemente usaría un tipo de tamaño fijo como int32_t en lugar de int.

Es posible que desee alguna alternativa a escribir el número en decimal para que quede más claro para aquellos que no noten la diferencia entre 2147483648 y 2174483648Pero tienes que tener cuidado.

Como se mencionó anteriormente, en una máquina de complemento a 2 de 32 bits, (int)(-2147483648) no se desbordará y por lo tanto está bien definido, porque -2147483648 se tratará como un tipo de signo más amplio. Sin embargo, no ocurre lo mismo con (int)(-0x80000000). 0x80000000 será tratado como un unsigned int (ya que encaja en la representación sin firmar); -0x80000000 está bien definido (pero el - no tiene efecto si int es de 32 bits), y la conversión de la resultante unsigned int 0x80000000 para int implica un desbordamiento. Para evitar el desbordamiento, debe convertir la constante hexadecimal en 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 32 bits (o menos) ints; solo se evaluará a 231 si int es de 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 long long int se requiere poder representar 263-1, por lo que su tamaño en bits debe ser mayor que 32. Por lo tanto, la forma

(int)(-(1LL<<31))

es posiblemente el más legible. YMMV.


Para cualquier pedante que pase, esta pregunta está etiquetada como C, y el último borrador de C (n1570.pdf) dice, con respecto a E1 << E2donde E1 tiene un tipo firmado, que el valor se define solo si E1 es no negativo y E1 × 2E2 “es representable en el tipo de resultado”. (§6.5.7 párrafo 4).

Eso es diferente de C++, en el que la aplicación del operador de desplazamiento a la izquierda se define si E1 es no negativo y E1 × 2E2 “es representable
en el tipo sin firmar correspondiente del tipo de resultado” (§5.8 párr. 2, énfasis agregado).

En C++, de acuerdo con el borrador estándar más reciente, la conversión de un valor entero a un tipo entero con signo es definido por 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 “el resultado está definido por la implementación o se emite una señal definida por la implementación”).

  • La forma convencional de definir INT_MIN es #define INT_MIN (-(INT_MAX)-1)(lo que evita el problema de tratar de tomar el negativo de un largo que describiste. (referencia)

    – abelenki

    11 de enero de 2016 a las 23:33


  • @abelenky: Sé que es así INT_MIN se define convencionalmente en máquinas de complemento a 2s. De lo contrario, supongo que tendría sentido definirlo como (-INT_MAX) porque tiene sentido escribir el número 2147483647 solo una vez. Pero se sugirió (razonablemente) que llamar a printf con -2147483647-1 es un poco extraño. Por supuesto, ahora que hemos tenido esta discusión, sabemos por qué tienes que hacer eso. Solo sugerí que usar un molde explícito le permite escribir el número literal en la forma en que normalmente lo escribirían aquellos que no son compiladores ni abogados de idiomas 🙂

    – rico

    11/01/2016 a las 23:40

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad