¿Hay alguna razón para no usar tipos enteros de ancho fijo (por ejemplo, uint8_t)?

Suponiendo que está utilizando un comstackdor que admita C99 (o incluso solo stdint.h), ¿hay alguna razón para no usar tipos enteros de ancho fijo como uint8_t?

Una razón de la que soy consciente es que tiene mucho más sentido usar char cuando se trata de caracteres en lugar de usar (u)int8_t s, como se menciona en esta pregunta .

Pero si planea almacenar un número, ¿cuándo le gustaría usar un tipo que no sepa qué tan grande es? Es decir, ¿en qué situación desearía almacenar un número en un unsigned short sin unsigned short sin saber si es 8, 16 o incluso 32 bits, en lugar de usar un uint16t ?

A partir de esto, ¿se considera una mejor práctica usar enteros de ancho fijo, o usar los tipos enteros normales y nunca asumir nada y usar sizeof donde se necesite saber cuántos bytes están usando?

En realidad, es bastante común almacenar un número sin necesidad de saber el tamaño exacto del tipo. Hay una gran cantidad de cantidades en mis progtwigs que razonablemente puedo suponer que no excederán los 2 mil millones, o hacer cumplir que no lo hacen. Pero eso no significa que necesite un tipo exacto de 32 bits para almacenarlos, cualquier tipo que pueda contar hasta al menos 2 mil millones está bien para mí.

Si está intentando escribir un código muy portátil, debe tener en cuenta que los tipos de ancho fijo son todos opcionales .

En una implementación de C99 donde CHAR_BIT es mayor que 8 no hay int8_t . La norma prohíbe que exista porque debería tener bits de relleno, y los tipos intN_t están definidos para no tener bits de relleno (7.18.1.1/1). uint8_t por uint8_t tanto también está prohibido porque (gracias, ouah) una implementación no puede definir uint8_t sin int8_t .

Entonces, en un código muy portable, si necesita un tipo firmado capaz de contener valores de hasta 127, debe usar uno de los caracteres signed char , int , int_least8_t o int_fast8_t según si desea solicitar al comstackdor que lo haga:

  • trabajar en C89 ( signed char o int )
  • evitar sorprendentes promociones enteras en expresiones aritméticas ( int )
  • pequeño ( int_least8_t o signed char )
  • rápido ( int_fast8_t o int )

Lo mismo ocurre con un tipo sin firmar de hasta 255, con unsigned char unsigned int , unsigned int , uint_least8_t y uint_fast8_t .

Si necesita una aritmética de módulo 256 en un código muy portátil, puede tomar el módulo usted mismo, enmascarar bits o jugar juegos con campos de bits.

En la práctica, la mayoría de la gente nunca necesita escribir código tan portátil. Por el momento CHAR_BIT > 8 solo aparece en hardware de propósito especial, y su código de propósito general no se usará en él. Por supuesto, eso podría cambiar en el futuro, pero si lo hace, sospecho que hay tanto código que hace suposiciones sobre Posix y / o Windows (ambos garantizan CHAR_BIT == 8 ), que el manejo de la no portabilidad de su código será una pequeña parte de un gran esfuerzo para transferir el código a esa nueva plataforma. Cualquier implementación de este tipo probablemente tendrá que preocuparse por cómo conectarse a Internet (lo que ocurre en octetos), mucho antes de que le preocupe cómo poner en funcionamiento su código 🙂

Si asumes que CHAR_BIT == 8 todos modos, entonces no creo que haya ninguna razón en particular para evitar (u)int8_t salvo que quieras que el código funcione en C89. Incluso en C89 no es tan difícil encontrar o escribir una versión de stdint.h para una implementación en particular. Pero si puede escribir fácilmente su código para que solo requiera que el tipo pueda contener 255 , en lugar de requerir que no pueda contener 256 , entonces también podría evitar la dependencia de CHAR_BIT == 8 .

Un problema que aún no se ha mencionado es que, si bien el uso de tipos enteros de tamaño fijo significará que los tamaños de las variables no cambiarán si los comstackdores usan tamaños diferentes para int , long , etc., no lo hará. necesariamente garantiza que el código se comportará de manera idéntica en máquinas con varios tamaños enteros, incluso cuando los tamaños están definidos .

Por ejemplo, dada la statement uint32_t i; , el comportamiento de la expresión (i-1) > 5 cuando i es cero variará dependiendo de si uint32_t es más pequeño que int . En sistemas donde eg int es 64 bits (y uint32_t es algo así como long short ), la variable i sería promovida a int ; la resta y la comparación se realizarían como firmadas (-1 es menor que 5). En los sistemas donde int es de 32 bits, la resta y la comparación se realizarían como unsigned int (la resta arrojaría un número realmente grande, que es mayor que cinco).

No sé cuánto código se basa en el hecho de que los resultados intermedios de las expresiones que involucran tipos sin firmar son necesarios para envolver incluso en ausencia de typecasts (en mi humilde opinión, si se desea el comportamiento de (uint32_t)(i-1) > 5 , el progtwigdor debería haber incluido un (uint32_t)(i-1) > 5 ) pero el estándar actualmente no permite ningún margen. Me pregunto qué problemas se plantearían si una regla que al menos permite que un comstackdor promueva operandos a un tipo de entero más largo en ausencia de typecasts o coerción de tipo [por ejemplo, dado uint32_t i,j , una asignación como j = (i+=1) >> 1; se requeriría cortar el desbordamiento, como lo haría j = (uint32_t)(i+1) >> 1; , pero j = (i+1)>>1 no] O, para el caso, qué tan difícil sería para los fabricantes de comstackdores garantizar que cualquier expresión de tipo integral cuyos resultados intermedios pudieran encajar dentro del tipo más grande firmado y no implicara desplazamientos a la derecha en cantidades no constantes, arrojaría el mismo resultados como si todos los cálculos se realizaron en ese tipo? Me parece bastante asqueroso que en una máquina donde int es de 32 bits:

   uint64_t a, b, c;
   ...
   a & = ~ 0x40000000;
   b & = ~ 0x80000000;
   c & = ~ 0x100000000;

borra un bit cada uno de a y c , pero borra los 33 bits superiores de b ; la mayoría de los comstackdores no darán pistas de que algo sea ‘diferente’ sobre la segunda expresión.

Es cierto que el ancho de un tipo entero estándar puede cambiar de una plataforma a otra, pero no su ancho mínimo.

Por ejemplo, el Estándar C especifica que un int es al menos de 16-bit y un long al menos 32-bit ancho de 32-bit .

Si no tiene alguna restricción de tamaño al almacenar sus objetos, puede dejar esto en la implementación. Por ejemplo, si su valor máximo con signo encajará en un 16-bit , puede simplemente usar un int . A continuación, deje que la implementación tenga la última palabra sobre cuál es el ancho int natural para la architecture a la que apunta la implementación.

Solo debe usar los tipos de ancho fijo cuando hace una suposición sobre el ancho.

uint8_t y unsigned char son los mismos en la mayoría de las plataformas, pero no en todas. El uso de uint8_t hincapié en el hecho de que supone una architecture con 8 bits de caracteres y no comstackría en otros, por lo que esta es una característica.

De lo contrario, usaría el typedef “semántico” como size_t , uintptr_t , ptrdiff_t porque reflejan mucho mejor lo que tienes en mente con los datos. Casi nunca utilizo los tipos de base directamente, int solo para los retornos de error, y no recuerdo haber usado short nunca.

Editar: Después de leer detenidamente C11, concluyo que uint8_t , si existe, debe ser unsigned char y no puede ser solo char incluso si ese tipo no está firmado. Esto proviene del requisito en 7.20.1 p1 de que todos intN_t y uintN_t deben ser los tipos firmados y no firmados correspondientes . El único par para los tipos de caracteres son signed char y unsigned char .

El código debe revelarle al lector casual (y al progtwigdor lo que es) lo que es importante. ¿Es solo un entero o un entero sin signo o incluso un entero con signo ? Lo mismo vale para el tamaño. ¿Es realmente importante para el algoritmo que alguna variable sea por defecto de 16 bits? ¿O es solo una microgestión innecesaria y un bash fallido de optimización?

Esto es lo que hace que la progtwigción sea un arte: mostrar lo que es importante.