tamaño - ¿Hay alguna razón para no usar tipos enteros de ancho fijo(por ejemplo, uint8_t)?
tipos de datos y su tamaño en bytes (5)
Suponiendo que está utilizando un compilador 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?
El código debe revelarle al lector casual (y al programador 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 intento fallido de optimización?
Esto es lo que hace que la programación sea un arte: mostrar lo que es importante.
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 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 arquitectura 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 arquitectura con 8 bits de caracteres y no compilarí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
.
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 compiladores 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 declaración 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 programador 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 compilador promueva operandos a un tipo entero más largo en ausencia de tipos de uint32_t i,j
o coerciones 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 compiladores 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 compiladores no darán pistas de que algo sea ''diferente'' sobre la segunda expresión.
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 programas 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 compilador que lo haga:
- trabajar en C89 (
signed char
oint
) - evitar sorprendentes promociones enteras en expresiones aritméticas (
int
) - pequeño (
int_least8_t
osigned char
) - rápido (
int_fast8_t
oint
)
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 los 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
.