registros - tipos de datos de longitud fija en C/C++
tipos de datos en c (11)
¿Alguien puede dar algún ejemplo, qué pasa, cuando el programa asume que int es 4 bytes, pero en una plataforma diferente es 2 bytes?
Supongamos que ha diseñado su programa para leer 100.000 entradas, y lo está contando utilizando un unsigned int
suponiendo un tamaño de 32 bits (las entradas unsigned int
de 32 bits pueden contar hasta 4.294.967.295). Si compila el código en una plataforma (o compilador) con enteros de 16 bits (las entradas de 16 bits sin signo pueden contar solo hasta 65.535) el valor se redondeará más allá de 65535 debido a la capacidad y denotará un conteo incorrecto.
He oído que el tamaño de los tipos de datos como int
puede variar en todas las plataformas.
Mi primera pregunta es: ¿alguien puede dar un ejemplo, qué pasa, cuando el programa asume que int
es 4 bytes, pero en una plataforma diferente es 2 bytes?
Otra pregunta que tuve está relacionada. Sé que la gente resuelve este problema con algunos typedefs
, como si tuvieras variables como u8
, u16
, u32
, que están garantizadas en 8bits, 16bits, 32bits, independientemente de la plataforma, mi pregunta es, ¿cómo se logra esto normalmente? (No me estoy refiriendo a los tipos de la biblioteca de stdint
. Tengo curiosidad por saber cómo se puede hacer para decir que un tipo es siempre de 32 bits, independientemente de la plataforma).
Sé que la gente resuelve este problema con algunos typedefs, como si tuvieras variables como u8, u16, u32, que están garantizadas en 8bits, 16bits, 32bits, independientemente de la plataforma
Hay algunas plataformas, que no tienen ningún tipo de tamaño determinado (como por ejemplo el 28xxx de TI, donde el tamaño del carácter es 16 bits). En tales casos, no es posible tener un tipo de 8 bits (a menos que realmente lo desee, pero eso puede generar un impacto en el rendimiento).
¿Cómo se logra esto usualmente?
Usualmente con typedefs. c99 (y c ++ 11) tienen estos typedefs en un encabezado . Entonces, solo úsalos.
¿Alguien puede dar algún ejemplo, qué pasa, cuando el programa asume que int es 4 bytes, pero en una plataforma diferente es 2 bytes?
El mejor ejemplo es una comunicación entre sistemas con diferentes tamaños de letra. Al enviar una matriz de entradas de una plataforma a otra, donde sizeof (int) es diferente en dos, hay que tener mucho cuidado.
Además, guarda una matriz de entradas en un archivo binario en la plataforma de 32 bits y la reinterpreta en una plataforma de 64 bits.
Tengo curiosidad de forma manual, ¿cómo se puede exigir que algún tipo sea siempre de 32 bits independientemente de la plataforma?
Si desea que su compilación (moderna) del programa C ++ falle si un tipo determinado no es del ancho que espera, agregue un static_assert
algún lugar. Agregaría esto donde se hacen las suposiciones sobre el ancho del tipo.
static_assert(sizeof(int) == 4, "Expected int to be four chars wide but it was not.");
chars
en las plataformas más utilizadas son de 8 bits de gran tamaño, pero no todas las plataformas funcionan de esta manera.
- Si un tipo es más pequeño de lo que piensas, es posible que no pueda almacenar un valor que necesites almacenar en él.
- Para crear un tipo de tamaño fijo, lea la documentación de las plataformas que se van a
#ifdef
y luego defina los tipos que se basan en#ifdef
para las plataformas específicas.
Bueno, primer ejemplo, algo como esto:
int a = 45000; // both a and b
int b = 40000; // does not fit in 2 bytes.
int c = a + b; // overflows on 16bits, but not on 32bits
Si cstdint
encabezado de cstdint
, encontrará cómo se definen todos los tipos de tamaño fijo ( int8_t
, uint8_t
, etc.) y lo único que varía entre diferentes arquitecturas es este archivo de encabezado. Entonces, en una arquitectura int16_t
podría ser:
typedef int int16_t;
y en otro:
typedef short int16_t;
Además, hay otros tipos, que pueden ser útiles, como: int_least16_t
En versiones anteriores del estándar C, generalmente hacía sus propias declaraciones typedef
para asegurarse de obtener un tipo (por ejemplo) de 16 bits, basado en cadenas #define
pasadas al compilador, por ejemplo:
gcc -DINT16_IS_LONG ...
Hoy en día (C99 y superior), hay tipos específicos como uint16_t
, el entero sin signo de ancho uint16_t
16 bits.
Siempre que incluya stdint.h
, obtendrá tipos de ancho de bits exactos, tipos al menos de ancho, tipos más rápidos con un ancho mínimo dado, etc., como se documenta en C99 7.18 Integer types <stdint.h>
. Si una implementación tiene tipos compatibles, se requiere que los proporcione.
También es muy útil inttypes.h
que agrega algunas características inttypes.h
para la conversión de formatos de estos nuevos tipos (cadenas de formato printf
y scanf
).
Los compiladores son responsables de obedecer el estándar. Cuando incluya <cstdint>
o <stdint.h>
, deberán proporcionar tipos de acuerdo con el tamaño estándar.
Los compiladores saben que están compilando el código para qué plataforma, luego pueden generar algunas macros internas o magias para construir el tipo adecuado. Por ejemplo, un compilador en una máquina de 32 bits genera macro __32BIT__
, y anteriormente tiene estas líneas en el archivo de encabezado stdint
:
#ifdef __32BIT__
typedef __int32_internal__ int32_t;
typedef __int64_internal__ int64_t;
...
#endif
y puedes usarlo
Para la primera pregunta: Desbordamiento de enteros .
Para la segunda pregunta: por ejemplo, para typedef
un entero de 32 bits sin signo, en una plataforma donde int
es 4 bytes, use:
typedef unsigned int u32;
En una plataforma donde int
tiene 2 bytes, mientras que long
tiene 4 bytes:
typedef unsigned long u32;
De esta forma, solo necesita modificar un archivo de encabezado para hacer que los tipos sean multiplataforma.
Si hay algunas macros específicas de plataforma, esto se puede lograr sin modificar manualmente:
#if defined(PLAT1)
typedef unsigned int u32;
#elif defined(PLAT2)
typedef unsigned long u32;
#endif
Si C99 stdint.h
es compatible, es preferible.
Primero que nada: nunca escriba programas que dependan del ancho de tipos como short
, int
, unsigned int
, ....
Básicamente: "nunca confíe en el ancho, si no está garantizado por el estándar".
Si quieres ser verdaderamente independiente de la plataforma y almacenar, por ejemplo, el valor 33000 como un entero con signo, no puedes simplemente asumir que un int
lo mantendrá. Un int
tiene al menos el rango -32767
a 32767
o -32768
a 32767
(dependiendo del complemento de uno / dos). Eso no es suficiente, a pesar de que generalmente es de 32 bits y, por lo tanto, es capaz de almacenar 33000. Para este valor, definitivamente necesitas un tipo de >16bit
, por lo tanto, simplemente eliges int32_t
o int64_t
. Si este tipo no existe, el compilador le dirá el error, pero no será un error silencioso.
Segundo: C ++ 11 proporciona un encabezado estándar para tipos enteros de ancho fijo. No se garantiza que ninguno de estos exista en su plataforma, pero cuando existen, se garantiza que tienen el ancho exacto. Consulte este artículo en cppreference.com para obtener una referencia. Los tipos se nombran en el formato int[n]_t
y uint[n]_t
donde n
es 8
, 16
, 32
o 64
. Deberá incluir el encabezado <cstdint>
. El encabezado C
es, por supuesto, <stdint.h>
.
las banderas de bits son el ejemplo trivial. 0x10000 le causará problemas, no puede enmascarar con él o comprobar si un bit se establece en esa posición 17 si todo se trunca o se rompe para que quepa en 16 bits.
por lo general, el problema ocurre cuando maximiza el número o cuando está serializando. Un escenario menos común ocurre cuando alguien hace una suposición de tamaño explícito.
En el primer escenario:
int x = 32000;
int y = 32000;
int z = x+y; // can cause overflow for 2 bytes, but not 4
En el segundo escenario,
struct header {
int magic;
int w;
int h;
};
entonces uno va a fwrite:
header h;
// fill in h
fwrite(&h, sizeof(h), 1, fp);
// this is all fine and good until one freads from an architecture with a different int size
En el tercer escenario:
int* x = new int[100];
char* buff = (char*)x;
// now try to change the 3rd element of x via buff assuming int size of 2
*((int*)(buff+2*2)) = 100;
// (of course, it''s easy to fix this with sizeof(int))
Si está utilizando un compilador relativamente nuevo, usaría uint8_t, int8_t, etc. para garantizar el tamaño del texto.
En compiladores más antiguos, typedef generalmente se define por plataforma. Por ejemplo, uno puede hacer:
#ifdef _WIN32
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
// and so on...
#endif
De esta manera, habría un encabezado por plataforma que define los detalles de esa plataforma.