Чтение двоичных данных (из файла) в структуру
Я читаю двоичные данные из файла, в частности из 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
без байтов?
Ответы
Ответ 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 не будут содержать каких-либо значимых данных, скорее всего. По крайней мере, не между прогонами программы, так как это указатели.