Me encontré con un punto interesante hoy en una revisión en Code Review . @Veedrac recomendó en esta respuesta que los tipos de tamaño variable (por ejemplo, int
y long
) sean reemplazados por tipos de tamaño fijo como uint64_t
y uint32_t
. Cita de los comentarios de esa respuesta:
Los tamaños de int y long (y, por lo tanto, los valores que pueden contener) dependen de la plataforma. Por otro lado, int32_t siempre tiene 32 bits de longitud. Usar int solo significa que su código funciona de manera diferente en diferentes plataformas, que generalmente no es lo que desea.
@Supercat explica en parte aquí el razonamiento detrás del estándar que no fija los tipos comunes . C fue escrito para ser portátil en todas las arquitecturas, a diferencia del ensamblaje que generalmente se usaba para la programación de sistemas en ese momento.
Creo que la intención del diseño fue originalmente que cada tipo que no sea int sea la cosa más pequeña que pueda manejar números de varios tamaños, y que int sea el tamaño "de propósito general" más práctico que pueda manejar +/- 32767.
En cuanto a mí, siempre he usado int
y no estoy realmente preocupado por las alternativas. Siempre he pensado que es el tipo más con el mejor rendimiento, final de la historia. El único lugar en el que pensé que el ancho fijo sería útil es cuando se codifican datos para almacenamiento o transferencia a través de una red. Raramente he visto tipos de ancho fijo en el código escrito por otros tampoco.
¿Estoy atrapado en los años 70 o hay realmente una razón para usar int
en la era de C99 y más allá?
fuente
Respuestas:
Existe un mito común y peligroso en el que los tipos como
uint32_t
salvar a los programadores de tener que preocuparse por el tamaño deint
. Si bien sería útil que el Comité de Normas definiera un medio para declarar enteros con una semántica independiente de la máquina, los tipos sin signouint32_t
tienen semántica demasiado floja para permitir que el código se escriba de manera limpia y portátil; Además, los tipos con signoint32
tienen semántica que, para muchas aplicaciones, se definen de manera innecesariamente estricta y, por lo tanto, excluyen lo que de otro modo serían optimizaciones útiles.Considere, por ejemplo:
En máquinas donde
int
no puede contener 4294967295, o puede contener 18446744065119617025, la primera función se definirá para todos los valores den
yexponent
, y su comportamiento no se verá afectado por el tamaño deint
; Además, el estándar no requerirá que produzca un comportamiento diferente en máquinas con cualquier tamaño deint
Algunos valores den
yexponent
, sin embargo, hará que invoque Comportamiento indefinido en máquinas donde 4294967295 es representable como unint
18446744065119617025 no lo es.La segunda función generará Comportamiento indefinido para algunos valores de
n
yexponent
en máquinas dondeint
no se puede mantener 4611686014132420609, pero generará un comportamiento definido para todos los valores den
yexponent
en todas las máquinas donde pueda (las especificacionesint32_t
implican que el comportamiento de envoltura del complemento a dos en máquinas donde es más pequeño queint
).Históricamente, a pesar de que el Estándar no dijo nada acerca de lo que los compiladores deberían hacer con el
int
desbordamientoupow
, los compiladores habrían producido consistentemente el mismo comportamiento que siint
hubiera sido lo suficientemente grande como para no desbordarse. Desafortunadamente, algunos compiladores más nuevos pueden tratar de "optimizar" los programas mediante la eliminación de comportamientos no obligatorios por el Estándar.fuente
pow
, ¡recuerde que este código es solo un ejemplo y no tiene en cuentaexponent=0
!exponent=1
dará como resultado que n se multiplique por sí mismo una vez, ya que el decremento se realiza después de la verificación, si el incremento se realiza antes de la verificación ( es decir, --exponente), no se realizará ninguna multiplicación y se devolverá n.N^(2^exponent)
, pero los cálculos de la forma aN^(2^exponent)
menudo se usan en el cálculo de funciones de exponenciación, y la exponenciación mod-4294967296 es útil para cosas como calcular el hash de la concatenación de dos cadenas cuya los hashes son conocidos.uint32_t
por 31 nunca producirá UB, pero la eficiencia manera de calcular 31 ^ N implica cálculos de 31 ^ (2 ^ N), lo que será.int32_t
a veces tener un desbordamiento definido y otras no, que es lo que parece mencionar, parece tener una importancia mínima en relación con el hecho de que, en primer lugar, me permite razonar sobre la prevención del desbordamiento. Y si desea un desbordamiento definido, es probable que desee que el módulo de resultados tenga un valor fijo, por lo que de todos modos está utilizando tipos de ancho fijo.Para valores estrechamente relacionados con los punteros (y, por lo tanto, con la cantidad de memoria direccionable) como los tamaños de búfer, los índices de matriz y Windows '
lParam
, tiene sentido tener un tipo entero con un tamaño dependiente de la arquitectura. Por lo tanto, los tipos de tamaño variable siguen siendo útiles. Esto es por eso que tenemos los typedefssize_t
,ptrdiff_t
,intptr_t
, etc. Ellos tienen que ser typedefs porque ninguno de los incorporados en C número entero tipos tiene por qué ser triple de tamaño.Así que la pregunta es si realmente
char
,short
,int
,long
, ylong long
siguen siendo útiles.IME, todavía es común que los programas C y C ++ lo usen
int
para la mayoría de las cosas. Y la mayoría de las veces (es decir, cuando sus números están en el rango ± 32 767 y no tiene requisitos de rendimiento rigurosos), esto funciona bien.Pero, ¿qué sucede si necesita trabajar con números en el rango de 17-32 bits (como las poblaciones de las grandes ciudades)? Podría usar
int
, pero eso sería codificar una dependencia de la plataforma. Si desea cumplir estrictamente con el estándar, puede usarlolong
, que tiene una garantía de al menos 32 bits.El problema es que el estándar C no especifica ningún tamaño máximo para un tipo entero. Hay implementaciones en las que
long
es de 64 bits, lo que duplica el uso de su memoria. Y si estoslong
son elementos de una matriz con millones de elementos, agotarás la memoria como un loco.Por lo tanto, ni
int
tampocolong
es un tipo adecuado para usar aquí si desea que su programa sea multiplataforma y eficiente en memoria. Introduzcaint_least32_t
.long
, evitando los problemas de truncamiento deint
int
, evitando el desperdicio de memoria de los 64 bitslong
.int
OTOH, suponga que no necesita grandes cantidades o grandes matrices, pero necesita velocidad. Y
int
puede ser lo suficientemente grande en todas las plataformas, pero no es necesariamente el tipo más rápido: los sistemas de 64 bits generalmente todavía tienen 32 bitsint
. Pero se puede usarint_fast16_t
y obtener el tipo “más rápido”, ya seaint
,long
olong long
.Por lo tanto, hay casos de uso prácticos para los tipos de
<stdint.h>
. Los tipos enteros estándar no significan nada. Especialmentelong
, que puede ser de 32 o 64 bits, y puede o no ser lo suficientemente grande como para contener un puntero, dependiendo del capricho de los escritores del compilador.fuente
uint_least32_t
es que sus interacciones con otros tipos están aún más débilmente especificadas que las deuint32_t
. En mi humilde opinión, el estándar debe definir tipos comouwrap32_t
yunum32_t
, con la semántica que cualquier compilador que define el tipouwrap32_t
, debe promocionar como un tipo sin signo en esencialmente los mismos casos que se promovería siint
fueran 32 bits, y cualquier compilador que defina el tipounum32_t
debe asegurarse de que Las promociones aritméticas básicas siempre lo convierten en un tipo con signo capaz de mantener su valor.intN_t
yuintN_t
, y cuyos comportamientos definidos serían consistentes conintN_t
yuintN_t
, pero que otorgaría a los compiladores cierta libertad en los valores asignados de código de caso fuera de su rango [permitiendo semánticas similares a las que fueron tal vez destinado parauint_least32_t
, pero sin incertidumbres como si agregar auint_least16_t
y anint32_t
produciría un resultado firmado o firmado.