Подтвердить что ты не робот

Чтение двоичных данных (из файла) в структуру

Я читаю двоичные данные из файла, в частности из 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 без байтов?

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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;

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

Обратите внимание, что 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;
}

Ответ 3

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

Ответ 4

Прошло некоторое время с тех пор, как я работал с 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, 2-байтовые элементы, чтобы составить 4-байтовое выравнивание.

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

Ответ 5

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