¿Se han reemplazado los tipos de ancho variable por tipos fijos en la C moderna?

21

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, inty long) sean reemplazados por tipos de tamaño fijo como uint64_ty 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 inten la era de C99 y más allá?

jacwah
fuente
1
Una parte de la gente simplemente imita a los demás. Creo que la mayoría del código de tipo de bit fijo se hizo sin conciencia. No hay razón para establecer el tamaño ni para no hacerlo. Tengo un código creado principalmente en plataformas de 16 bits (MS-DOS y Xenix de los 80), que solo se compila y ejecuta hoy en cualquier 64 y se beneficia del nuevo tamaño de palabra y direccionamiento, solo compilándolo. Es decir que la serialización para exportar / importar datos es un diseño de arquitectura muy importante para mantenerlo portátil.
Luciano

Respuestas:

7

Existe un mito común y peligroso en el que los tipos como uint32_tsalvar a los programadores de tener que preocuparse por el tamaño de int. 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 signo uint32_ttienen semántica demasiado floja para permitir que el código se escriba de manera limpia y portátil; Además, los tipos con signo int32tienen 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:

uint32_t upow(uint32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

int32_t spow(int32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

En máquinas donde intno puede contener 4294967295, o puede contener 18446744065119617025, la primera función se definirá para todos los valores de ny exponent, y su comportamiento no se verá afectado por el tamaño de int; Además, el estándar no requerirá que produzca un comportamiento diferente en máquinas con cualquier tamaño deint Algunos valores de ny exponent, 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 ny exponenten máquinas donde intno se puede mantener 4611686014132420609, pero generará un comportamiento definido para todos los valores den y exponenten todas las máquinas donde pueda (las especificaciones int32_timplican que el comportamiento de envoltura del complemento a dos en máquinas donde es más pequeño que int).

Históricamente, a pesar de que el Estándar no dijo nada acerca de lo que los compiladores deberían hacer con el intdesbordamiento upow, los compiladores habrían producido consistentemente el mismo comportamiento que si inthubiera 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.

Super gato
fuente
3
Cualquiera que quiera implementarlo manualmente pow, ¡recuerde que este código es solo un ejemplo y no tiene en cuenta exponent=0!
Mark Hurd
1
Creo que debería usar el operador de decremento de prefijo, no el postfix, actualmente está haciendo 1 multiplicación adicional, por ejemplo, exponent=1dará 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.
ALXGTV
2
@MarkHurd: la función tiene un nombre pobre, ya que lo que realmente calcula es N^(2^exponent), pero los cálculos de la forma a N^(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.
supercat
1
@ALXGTV: la función debía ser ilustrativa de algo que computaba algo relacionado con el poder. Lo que realmente calcula es N ^ (2 ^ exponente), que es parte de calcular eficientemente el exponente de N ^, y bien puede fallar incluso si N es pequeño (la multiplicación repetida de a uint32_tpor 31 nunca producirá UB, pero la eficiencia manera de calcular 31 ^ N implica cálculos de 31 ^ (2 ^ N), lo que será.
supercat
No creo que este sea un buen argumento. El objetivo no es hacer funciones definidas para todas las entradas, sensibles o no; es poder razonar sobre tamaños y desbordamientos. int32_ta 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.
Veedrac
4

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 typedefs size_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 intpara 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 usarlo long, 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 longes 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 inttampoco longes un tipo adecuado para usar aquí si desea que su programa sea multiplataforma y eficiente en memoria. Introduzca int_least32_t.

  • Su compilador I16L32 le ofrece 32 bits long, evitando los problemas de truncamiento deint
  • Su compilador I32L64 le ofrece 32 bits int, evitando el desperdicio de memoria de los 64 bits long.
  • Su compilador I36L72 le ofrece 36 bits int

OTOH, suponga que no necesita grandes cantidades o grandes matrices, pero necesita velocidad. Y intpuede 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 bits int. Pero se puede usar int_fast16_ty obtener el tipo “más rápido”, ya sea int, longolong long .

Por lo tanto, hay casos de uso prácticos para los tipos de <stdint.h>. Los tipos enteros estándar no significan nada. Especialmente long, 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.

dan04
fuente
Un problema con tipos como uint_least32_tes que sus interacciones con otros tipos están aún más débilmente especificadas que las de uint32_t. En mi humilde opinión, el estándar debe definir tipos como uwrap32_ty unum32_t, con la semántica que cualquier compilador que define el tipo uwrap32_t, debe promocionar como un tipo sin signo en esencialmente los mismos casos que se promovería si intfueran 32 bits, y cualquier compilador que defina el tipo unum32_tdebe asegurarse de que Las promociones aritméticas básicas siempre lo convierten en un tipo con signo capaz de mantener su valor.
supercat
Además, el Estándar también podría definir tipos cuyo almacenamiento y alias eran compatibles con intN_ty uintN_t, y cuyos comportamientos definidos serían consistentes con intN_ty uintN_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 para uint_least32_t, pero sin incertidumbres como si agregar a uint_least16_ty an int32_tproduciría un resultado firmado o firmado.
supercat