Читать двоичные данные (из файла) в структуру - PullRequest
12 голосов
/ 21 октября 2010

Я читаю двоичные данные из файла, в частности из ZIP-файла. (Чтобы узнать больше о структуре формата zip см. http://en.wikipedia.org/wiki/ZIP_%28file_format%29)

Я создал структуру, которая хранит данные:

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;

Размер, возвращаемый sizeof(ZIP_local_file_header), равен 40, но если сумма каждого поля вычисляется с помощью оператора sizeof, общий размер равен 38.

Если у нас есть следующая структура:

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

sizeof(FOO) возвращает 8, поскольку память каждый раз выделяется 4 байтами. Итак, для выделения x зарезервировано 4 байта (но реальный размер составляет 2 байта). Если нам понадобится еще short int, он заполнит оставшиеся 2 байта предыдущего распределения. Но поскольку у нас есть int, он будет выделен плюс 4 байта, а пустые 2 байта потрачены впустую.

Для чтения данных из файла мы можем использовать функцию fread:

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

Но поскольку в середине есть пустые байты, они не читаются правильно.

Что я могу сделать для последовательного и эффективного хранения данных с ZIP_local_file_header, не тратя байтов?

Ответы [ 5 ]

10 голосов
/ 21 октября 2010

Чтобы соответствовать требованиям выравнивания базовой платформы, структуры могут иметь байты "заполнения" между элементами, так что каждый элемент начинается с правильно выровненного адреса.

Есть несколько способов обойти это: один - прочитать каждый элемент заголовка отдельно, используя член соответствующего размера:

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

Другой вариант - использовать битовые поля в определении структуры; на них не распространяются ограничения заполнения. Недостатком является то, что битовые поля должны быть unsigned int или int или, по состоянию на C99, _Bool; вам может потребоваться привести необработанные данные к целевому типу, чтобы правильно их интерпретировать:

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;

Вам также может потребоваться выполнить некоторую замену байтов в каждом элементе, если файл был написан с прямым порядком байтов, но ваша система имеет младший порядок байтов.

Обратите внимание, что name и extra field не являются частью определения структуры; когда вы читаете из файла, вы не будете читать указатель значения для имени и дополнительного поля, вы будете читать фактическое содержимое имени и дополнительное поле Поскольку вы не знаете размеры этих полей до тех пор, пока не прочитаете остальную часть заголовка, вам следует отложить их чтение до тех пор, пока вы не прочтете структуру выше. Что-то вроде

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;
}
9 голосов
/ 21 октября 2010

C struct - это просто группировка связанных частей данных, они не указывают конкретный макет в памяти .(Так же, как ширина int также не определена.) Младший / старший-порядок также не определен и зависит от процессора.

Различные компиляторы, один и тот же компилятор на разных архитектурахили операционные системы и т. д., все структуры компоновки будут по-разному.

Поскольку формат файла, который вы хотите прочитать, определяется в зависимости от того, куда идут байты, структура, хотя и выглядит очень удобной и заманчивой, неправильное решение.Вам нужно обработать файл как char[], извлечь нужные байты и сдвинуть их, чтобы сделать числа, состоящие из нескольких байтов и т. Д.

5 голосов
/ 21 октября 2010

Решение зависит от компилятора, но, например, в GCC вы можете заставить его упаковать структуру более плотно, добавив __attribute__((packed)) к определению.Смотри http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc/Type-Attributes.html.

2 голосов
/ 21 октября 2010

Прошло много времени с тех пор, как я работал со сжатыми zip-файлами, но я помню практику добавления собственного отступа для соответствия 4-байтным правилам выравнивания арки PowerPC.

В лучшем случае вам просто нужно определить каждый элемент вашей структуры в соответствии с размером фрагмента данных, который вы хотите прочитать. Не просто используйте 'int', поскольку это может быть платформа / компилятор, определенный для различных размеров.

Сделайте что-то подобное в шапке:

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

Тогда вместо просто int используйте unsigned32, где у вас есть известный 4-байтовый vaule. И unsigned16 для любых известных 2-байтовых значений.

Это поможет вам увидеть, где вы можете добавить байты заполнения для совпадения с 4-байтовым выравниванием или где вы можете сгруппировать 2-байтовые элементы для создания 4-байтового выравнивания.

В идеале вы можете использовать минимум байтов заполнения (который можно использовать для добавления дополнительных данных позже при расширении программы) или вообще ни одного, если вы можете выровнять все по 4-байтовым границам с данными переменной длины в конце .

0 голосов
/ 21 октября 2010

Кроме того, имя и extra_field, скорее всего, не будут содержать значимых данных. По крайней мере, между запусками программы, так как это указатели.

...