Почему 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-байтных границах. Компилятор полностью осознает все это и вставляет скрытые байты заполнения в вашу структуру, поэтому он всегда работает правильно. Но в результате спрятанных байтов заполнения, совершенно небезопасно предположить, что строковые байты выложены в памяти, как вы думаете. Если вам очень повезло, вы работаете на компьютере, который использует порядок байтов байтов и вообще не имеет ограничений по выравниванию, поэтому вы можете размещать структуры непосредственно над файлами и работать с ними. Но вам, вероятно, не повезло - конечно, программы, которые должны быть "переносимыми" на разные машины, должны избегать попытки структурирования непосредственно над любой частью любого файла.