2010-10-21 49 views
12

estoy leyendo los datos binarios de un archivo, en concreto de un archivo zip. (Para saber más sobre la estructura de formato zip ver http://en.wikipedia.org/wiki/ZIP_%28file_format%29)leer datos binarios (de archivo) en una estructura

He creado una estructura que almacena los datos:

typedef struct { 
              /*Start Size   Description         */ 
    int signatute;       /* 0 4 Local file header signature = 0x04034b50    */ 
    short int version;      /*  4 2 Version needed to extract (minimum)      */ 
    short int bit_flag;      /*  6 2 General purpose bit flag        */ 
    short int compression_method;   /*  8 2 Compression method          */ 
    short int time;       /* 10 2 File last modification time        */ 
    short int date;       /* 12 2 File last modification date        */ 
    int crc;        /* 14 4 CRC-32             */ 
    int compressed_size;     /* 18 4 Compressed size           */ 
    int uncompressed_size;     /* 22 4 Uncompressed size          */ 
    short int name_length;     /* 26 2 File name length (n)         */ 
    short int extra_field_length;   /* 28 2 Extra field length (m)         */ 
    char *name;        /* 30 n File name            */ 
    char *extra_field;      /*30+n m Extra field            */ 

} ZIP_local_file_header; 

El tamaño devuelto por sizeof(ZIP_local_file_header) es de 40, pero si la suma de cada campo es calculada con sizeof operador del tamaño total es de 38.

Si tenemos la siguiente estructura:

typedef struct { 
    short int x; 
    int y; 
} FOO; 

sizeof(FOO) devuelve 8 porque la memoria está asignada con 4 bytes cada vez. Por lo tanto, para asignar x se reservan 4 bytes (pero el tamaño real es de 2 bytes). Si necesitamos otro short int, rellenará los 2 bytes restantes de la asignación anterior. Pero como tenemos un int, se asignará más 4 bytes y los 2 bytes vacíos se perderán.

para leer datos de archivo, podemos utilizar la función fread:

ZIP_local_file_header p; 
fread(&p,sizeof(ZIP_local_file_header),1,file); 

Pero como hay bytes vacíos en el medio, no se lee correctamente.

¿Qué se puede hacer para almacenar de forma secuencial y eficiente de datos con ZIP_local_file_header perder ningún byte?

+0

http://stackoverflow.com/questions/3913119/dumping-memory-to-file/3913152#3913152 <- posible duplicado –

+3

Pregunta muy bien escrita. –

Respuesta

9

C struct s están a punto de agrupar datos relacionados juntos, ellos no especifican un diseño particular en la memoria. (Al igual que el ancho de un int no se define tampoco.) Poco-endian/Big-endian también no se define, y depende del procesador.

diferentes compiladores, el mismo compilador en diferentes arquitecturas o sistemas operativos, etc., todas las estructuras de diseño diferente.

Como el formato de archivo que desea leer se define en términos de qué bytes ir a donde, una estructura, aunque parece muy conveniente y tentador, no es la solución correcta. Debe tratar el archivo como char[] y extraer los bytes que necesita y cambiarlos para hacer números compuestos de bytes múltiples, etc.

+0

+1 para proponer la solución portátil. –

+0

Es la solución que tengo. Pero hace que la lectura sea más compleja y dependiente de la estructura. – rigon

+7

los miembros de la estructura se presentarán en el orden en que se declaran. De 6.7.2.1, párrafo 13: "Dentro de un objeto de estructura, los miembros de campo no de bit y las unidades en que residen los campos de bit tienen direcciones que * aumentan en el orden en que se declaran *. Un puntero a un objeto de estructura, convenientemente convertido, apunta a su miembro inicial (o si ese miembro es un campo de bits , luego a la unidad en la que reside), y viceversa.Puede haber relleno sin nombre dentro de un objeto de estructura, pero no al principio. "Énfasis mío. –

0

Además, el nombre y extra_field no contendrán datos significativos, lo más probable. Al menos no entre ejecuciones del programa, ya que estos son punteros.

+0

Lo sé, pero mi problema es porque tengo 5 'short int' y la memoria asignada es de 8 bytes, pero solo se usan 6. – rigon

9

Para cumplir con los requisitos de alineación de la plataforma subyacente, las estructuras pueden tener "relleno" de bytes entre miembros para que cada miembro comience en una dirección alineada correctamente.

Hay varias maneras de evitar esto: uno es para leer cada elemento de la cabecera por separado utilizando el miembro de tamaño adecuado:

fread(&p.signature, sizeof p.signature, 1, file); 
fread(&p.version, sizeof p.version, 1, file); 
... 

Otra es utilizar campos de bits en su definición de estructura; estos no están sujetos a restricciones de relleno. La desventaja es que los campos de bits deben ser unsigned int o int o, a partir de C99, _Bool; puede que tenga que emitir los datos en bruto al tipo de destino para interpretar correctamente:

typedef struct {     
    unsigned int signature   : 32; 
    unsigned int version   : 16;     
    unsigned int bit_flag;   : 16;     
    unsigned int compression_method : 16;    
    unsigned int time    : 16; 
    unsigned int date    : 16; 
    unsigned int crc    : 32; 
    unsigned int compressed_size : 32;     
    unsigned int uncompressed_size : 32; 
    unsigned int name_length  : 16;  
    unsigned int extra_field_length : 16; 
} ZIP_local_file_header; 

Es posible que también tenga que hacer algunas byte-swapping en cada miembro si el archivo fue escrito en big-endian, pero el sistema es little-endian.

Tenga en cuenta que name y extra field no son parte de la definición de estructura; cuando lea del archivo, no va a leer puntero valores para el nombre y el campo adicional, va a estar leyendo contenidos reales del nombre y el campo adicional. Como no conoce el tamaño de esos campos hasta que lea el resto del encabezado, debe posponer la lectura hasta después de haber leído la estructura anterior. Algo así como

ZIP_local_file_header p; 
char *name = NULL; 
char *extra = NULL; 
... 
fread(&p, sizeof p, 1, file); 
if (name = malloc(p.name_length + 1)) 
{ 
    fread(name, p.name_length, 1, file); 
    name[p.name_length] = 0; 
} 
if (extra = malloc(p.extra_field_length + 1)) 
{ 
    fread(extra, p.extra_field_length, 1, file); 
    extra[p.extra_field_length] = 0; 
} 
+0

Muy buena explicación. Pero si paso un puntero de estructura a función y uso la dirección del campo, tengo un error: zip.c: 42: 2: error: no se puede tomar la dirección del campo de bit 'firma' zip.c: 42: 2: error: 'sizeof' aplicado a un campo de bits – rigon

+2

@Ricardo - debe pasar los punteros a los miembros de la estructura como se define en su * original * struct type ** o ** usar bitfields y pasar la dirección de toda la estructura. No puede tomar la dirección de un bit campo. –

2

Ha sido un tiempo desde que trabajé con archivos zip-comprimido, pero recuerdo la práctica de añadir mi propia relleno para golpear las reglas de alineación de 4 bytes de PowerPC arco.

En el mejor de los casos, simplemente necesita definir cada elemento de su estructura al tamaño de la información que desea leer. No utilice simplemente 'int', ya que puede ser plataforma/compilador definido para varios tamaños.

hacer algo como esto en un encabezado:

typedef unsigned long unsigned32; 
typedef unsigned short unsigned16; 
typedef unsigned char unsigned8; 
typedef unsigned char byte; 

A continuación, en lugar de simplemente utilizar un int unsigned32 donde se tiene una conocida vaule de 4 bytes. Y unsigned16 para cualquier valor conocido de 2 bytes.

Esto le ayudará a ver dónde puede agregar bytes de relleno para ajustar la alineación de 4 bytes, o donde puede agrupar 2, elementos de 2 bytes para formar una alineación de 4 bytes.

Idealmente, puede utilizar un mínimo de bytes de relleno (que pueden usarse para agregar datos adicionales más adelante al expandir el programa) o ninguno si puede alinear todo a límites de 4 bytes con datos de longitud variable en el fin.

Cuestiones relacionadas