manejo - Escribir contenido de estructura sin procesar(bytes) en un archivo en C. Confundido sobre el tamaño real escrito
leer un archivo y guardarlo en un arreglo c (8)
Pregunta básica, pero esperaba que esta estructura ocupara 13 bytes de espacio (1 para el carácter, 12 para los 3 elementos no firmados). En cambio, sizeof(ESPR_REL_HEADER)
me da 16 bytes.
typedef struct {
unsigned char version;
unsigned int root_node_num;
unsigned int node_size;
unsigned int node_count;
} ESPR_REL_HEADER;
Lo que trato de hacer es inicializar esta estructura con algunos valores y escribir los datos que contiene (los bytes sin procesar) al comienzo de un archivo, de modo que cuando abra este archivo, más tarde pueda reconstruir esta estructura y obtener meta datos sobre lo que contiene el resto del archivo.
Estoy inicializando la estructura y escribiéndola en el archivo de esta manera:
int esprime_write_btree_header(FILE * fp, unsigned int node_size) {
ESPR_REL_HEADER header = {
.version = 1,
.root_node_num = 0,
.node_size = node_size,
.node_count = 1
};
return fwrite(&header, sizeof(ESPR_REL_HEADER), 1, fp);
}
Donde node_size
es actualmente 4 mientras experimento.
El archivo contiene los siguientes datos después de escribirle la estructura:
-bash$ hexdump test.dat
0000000 01 bf f9 8b 00 00 00 00 04 00 00 00 01 00 00 00
0000010
Espero que realmente contenga:
-bash$ hexdump test.dat
0000000 01 00 00 00 00 04 00 00 00 01 00 00 00
0000010
Disculpe la felicidad. Estoy tratando de aprender :) ¿Cómo puedo escribir eficientemente solo los componentes de datos de mi estructura en un archivo?
¡Intenta no hacer esto! La discrepancia de tamaño es causada por el relleno y la alineación utilizada por los compiladores / vinculadores para optimizar los accesos a los vars por velocidad. Las reglas de relleno y alineación con lenguaje y sistema operativo. Además, escribir y leer en diferentes hardware puede ser problemático debido a la endianidad.
Escribe tus metadatos byte a byte en una estructura que no se puede malinterpretar. Las cadenas ASCII terminadas en nulo son correctas.
Cuando se escriben estructuras tal como están con fwrite
, se escribe entonces tal como están en la memoria, incluidos los "bytes muertos" dentro de la estructura que se insertan debido al relleno . Además, sus datos de múltiples bytes se escriben con los endiannes de su sistema.
Si no quiere que suceda, escriba una función que serialice los datos de su estructura. Puede escribir solo las áreas no acolchadas, y también escribir datos multibyte en un orden predecible (por ejemplo, en el orden de bytes de la red ).
Eso es debido al relleno de la estructura, ver http://en.wikipedia.org/wiki/Sizeof#Implementation
Esto se debe a algo llamado alineación de memoria. La primera char se extiende para tomar 4 bytes de memoria. De hecho, los tipos más grandes como int
solo pueden "comenzar" al comienzo de un bloque de 4 bytes, por lo que el compilador rellenará con bytes para llegar a este punto.
Tuve el mismo problema con el encabezado de mapa de bits, comenzando con 2 char. char bm[2]
un char bm[2]
dentro de la estructura y me pregunté por 2 días donde el # $% ^ el 3er y el 4to octeto del encabezado estaban yendo ...
Si desea evitar esto, puede usar __attribute__((packed))
pero tenga cuidado, la alineación de memoria ES necesaria para que su programa se ejecute convenientemente .
La estructura está sujeta a las reglas de alineación, lo que significa que algunos elementos se rellenan. Mirándolo, parece que el primer campo de caracteres unsigned char
ha sido rellenado a 4 bytes.
Una de las trampas aquí es que las reglas pueden ser diferentes de un sistema a otro, así que si escribes la estructura como un todo usando fwrite
en un programa compilado con un compilador en una plataforma, y luego tratas de leerlo usando fread
en otro, podría obtener basura porque el segundo programa asumirá que los datos están alineados para ajustarse a su concepción del diseño de la estructura.
En general, tienes que:
Decida que los archivos de datos guardados solo son válidos para compilaciones de su programa que comparten ciertas características (dependiendo del comportamiento documentado del compilador que utilizó), o
No se escribe una estructura completa como una, sino que se implementa un formato de datos más formal donde cada elemento se escribe individualmente con su tamaño controlado explícitamente.
(Un problema relacionado es que el orden de bytes podría ser diferente, la misma opción generalmente se aplica también allí, excepto que en la opción 2 desea especificar explícitamente el orden de bytes del formato de datos).
Los microprocesadores no están diseñados para captar datos de direcciones arbitrarias. Los objetos como int
4 bytes solo se deben almacenar en direcciones divisibles por cuatro. Este requisito se llama alineación .
C le da al compilador libertad para insertar bytes de relleno entre los miembros de la estructura para alinearlos. La cantidad de relleno es solo una variable entre diferentes plataformas, otra variable principal es la endianidad . Esta es la razón por la cual no debe simplemente "volcar" estructuras en el disco si desea que el programa se ejecute en más de una máquina.
La mejor práctica es escribir cada miembro explícitamente, y usar htonl
para fijar la htonl
a big-endian antes del resultado binario. Al leer de nuevo, use memcpy
para mover bytes sin procesar, no use
char *buffer_ptr;
...
++ buffer_ptr;
struct.member = * (int *) buffer_ptr; /* potential alignment error */
pero en lugar de hacer
memcpy( buffer_ptr, (char *) & struct.member, sizeof struct.member );
struct.member = ntohl( struct.member ); /* if member is 4 bytes */
Si desea escribir los datos en un formato específico, use matrices de caracteres unsigned char
...
unsigned char outputdata[13];
outputdata[0] = 1;
outputdata[1] = 0;
/* ... of course, use data from struct ... */
outputdata[12] = 0;
fwrite(outputdata, sizeof outputdata, 1, fp);
Uso un código de código abierto impresionante escrito por Troy D. Hanson llamado TPL: http://tpl.sourceforge.net/ . Con TPL no tiene ninguna dependencia externa. Es tan simple como incluir tpl.c y tpl.h en su propio programa y usar TPL API.
Aquí está la guía: http://tpl.sourceforge.net/userguide.html