Почему fread беспорядок с моим байтом порядке?
Im пытается разобрать файл bmp с помощью fread()
, и когда я начинаю анализировать, он меняет порядок моих байтов.
typedef struct{
short magic_number;
int file_size;
short reserved_bytes[2];
int data_offset;
}BMPHeader;
...
BMPHeader header;
...
Шестнадцатеричные данные 42 4D 36 00 03 00 00 00 00 00 36 00 00 00
;
Я загружаю шестнадцатеричные данные в структуру fread(&header,14,1,fileIn);
Моя проблема в том, где магическое число должно быть 0x424d //'BM'
fread(), которое переворачивает байты в 0x4d42 // 'MB'
Почему fread() делает это и как я могу его исправить,
EDIT: Если я недостаточно определен, мне нужно прочитать весь фрагмент шестнадцатеричных данных в структуре, а не только магическое число. В качестве примера я выбрал только магическое число.
Ответы
Ответ 1
Это не ошибка fread
, а вашего процессора, который (по-видимому) мало-endian. То есть ваш процессор обрабатывает первый байт в значении short
как младшие 8 бит, а не (как вы ожидали, ожидали) высокие 8 бит.
Всякий раз, когда вы читаете формат двоичного файла, вы должны явно преобразовать его из формата файла в исходную спецификацию ЦП. Вы делаете это с помощью таких функций:
/* CHAR_BIT == 8 assumed */
uint16_t le16_to_cpu(const uint8_t *buf)
{
return ((uint16_t)buf[0]) | (((uint16_t)buf[1]) << 8);
}
uint16_t be16_to_cpu(const uint8_t *buf)
{
return ((uint16_t)buf[1]) | (((uint16_t)buf[0]) << 8);
}
Вы делаете свой fread
в буфер uint8_t
соответствующего размера, а затем вручную копируете все байты данных в структуру BMPHeader
, преобразовывая при необходимости. Это будет выглядеть примерно так:
/* note adjustments to type definition */
typedef struct BMPHeader
{
uint8_t magic_number[2];
uint32_t file_size;
uint8_t reserved[4];
uint32_t data_offset;
} BMPHeader;
/* in general this is _not_ equal to sizeof(BMPHeader) */
#define BMP_WIRE_HDR_LEN (2 + 4 + 4 + 4)
/* returns 0=success, -1=error */
int read_bmp_header(BMPHeader *hdr, FILE *fp)
{
uint8_t buf[BMP_WIRE_HDR_LEN];
if (fread(buf, 1, sizeof buf, fp) != sizeof buf)
return -1;
hdr->magic_number[0] = buf[0];
hdr->magic_number[1] = buf[1];
hdr->file_size = le32_to_cpu(buf+2);
hdr->reserved[0] = buf[6];
hdr->reserved[1] = buf[7];
hdr->reserved[2] = buf[8];
hdr->reserved[3] = buf[9];
hdr->data_offset = le32_to_cpu(buf+10);
return 0;
}
Вы не предполагаете, что конечная точка процессора совпадает с форматом файла, даже если вы знаете, что сейчас они одинаковы; вы все равно пишете конверсии, так что в будущем ваш код будет работать без изменений на процессоре с противоположной точностью.
Вы можете сделать жизнь проще для себя, используя типы фиксированной ширины <stdint.h>
, используя неподписанные типы, если не иметь возможности представлять отрицательные числа, и не использовать целые числа, когда будут выполняться массивы символов. Я сделал все это в приведенном выше примере. Вы можете видеть, что вам не нужно беспокоить endian-преобразование магического числа, потому что единственное, что вам нужно сделать, это тест magic_number[0]=='B' && magic_number[1]=='M'
.
Преобразование в противоположном направлении, кстати, выглядит так:
void cpu_to_le16(uint8_t *buf, uint16_t val)
{
buf[0] = (val & 0x00FF);
buf[1] = (val & 0xFF00) >> 8;
}
void cpu_to_be16(uint8_t *buf, uint16_t val)
{
buf[0] = (val & 0xFF00) >> 8;
buf[1] = (val & 0x00FF);
}
Преобразование 32-/64-битных величин, оставшихся в виде упражнения.
Ответ 2
Я предполагаю, что это вопрос конца. т.е. вы помещаете байты 42
и 4D
в значение short
. Но ваша система немногочисленна (я мог бы иметь неправильное имя), которая фактически считывает байты (в многобайтовом целочисленном типе) слева направо, а не справа налево.
Продемонстрировано в этом коде:
#include <stdio.h>
int main()
{
union {
short sval;
unsigned char bval[2];
} udata;
udata.sval = 1;
printf( "DEC[%5hu] HEX[%04hx] BYTES[%02hhx][%02hhx]\n"
, udata.sval, udata.sval, udata.bval[0], udata.bval[1] );
udata.sval = 0x424d;
printf( "DEC[%5hu] HEX[%04hx] BYTES[%02hhx][%02hhx]\n"
, udata.sval, udata.sval, udata.bval[0], udata.bval[1] );
udata.sval = 0x4d42;
printf( "DEC[%5hu] HEX[%04hx] BYTES[%02hhx][%02hhx]\n"
, udata.sval, udata.sval, udata.bval[0], udata.bval[1] );
return 0;
}
Дает следующий выход
DEC[ 1] HEX[0001] BYTES[01][00]
DEC[16973] HEX[424d] BYTES[4d][42]
DEC[19778] HEX[4d42] BYTES[42][4d]
Итак, если вы хотите быть переносимым, вам нужно будет определить конечную систему вашей системы, а затем при необходимости выполнить байтовую перетасовку. В Интернете будет множество примеров обмена байтами.
Последующий вопрос:
Я спрашиваю только потому, что размер моего файла равен 3 вместо 196662
Это связано с проблемами выравнивания памяти. 196662 - это байты 36 00 03 00
, а 3 - байты 03 00 00 00
. В большинстве систем такие типы, как int
и т.д., Не должны разбиваться на несколько памяти words
. Так что интуитивно вы думаете, что ваша структура выложена в память, например:
Offset
short magic_number; 00 - 01
int file_size; 02 - 05
short reserved_bytes[2]; 06 - 09
int data_offset; 0A - 0D
НО в 32-битной системе, которая означает, что files_size
имеет 2 байта в том же word
как magic_number
и два байта в следующем word
. Большинство компиляторов не выдерживают этого, поэтому способ компоновки структуры в памяти на самом деле похож:
short magic_number; 00 - 01
<<unused padding>> 02 - 03
int file_size; 04 - 07
short reserved_bytes[2]; 08 - 0B
int data_offset; 0C - 0F
Итак, когда вы читаете, что ваш поток байтов в 36 00
входит в вашу область заполнения, которая оставляет ваш файл_размер как получение 03 00 00 00
. Теперь, если вы использовали fwrite
для создания этих данных, это должно было быть ОК, поскольку байты заполнения были бы записаны. Но если ваш вход всегда будет в указанном вами формате, нецелесообразно читать всю структуру как одну с fread. Вместо этого вам нужно будет прочитать каждый из элементов по отдельности.
Ответ 3
Написание структуры в файл очень не переносимо - безопасно просто не пытаться это делать вообще. Использование такой структуры гарантируется, что она работает только в том случае, если: a) структура написана и прочитана как структура (никогда не существует последовательности байтов), а b) она всегда записывается и читается на одной и той же машине (типа). Мало того, что существуют "конечные" проблемы с разными процессорами (что, как вам кажется, вы столкнулись), есть также проблемы с выравниванием. Различные аппаратные реализации имеют разные правила размещения целых чисел только на четных двухбайтовых или даже 4-байтных или даже 8-байтных границах. Компилятор полностью осознает все это и вставляет скрытые байты заполнения в вашу структуру, поэтому он всегда работает правильно. Но в результате спрятанных байтов заполнения, совершенно небезопасно предположить, что строковые байты выложены в памяти, как вы думаете. Если вам очень повезло, вы работаете на компьютере, который использует порядок байтов байтов и вообще не имеет ограничений по выравниванию, поэтому вы можете размещать структуры непосредственно над файлами и работать с ними. Но вам, вероятно, не повезло - конечно, программы, которые должны быть "переносимыми" на разные машины, должны избегать попытки структурирования непосредственно над любой частью любого файла.