Почему 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: если я не был достаточно конкретным, мне нужно прочитать весь кусок шестнадцатеричных данных в структуру входит не только магическое число. Я выбрал магическое число только в качестве примера.

3 ответов


это не вина fread, но вашего процессора, который (по-видимому) мало-endian. То есть ваш процессор обрабатывает первый байт в short значение низкий 8 бит, а не (как вы, кажется, ожидали) высокий 8 бит.

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

/* 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 struct, преобразование по мере необходимости. Это будет выглядеть примерно так:

/* 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;
}

ты не предположим, что endianness процессора совпадает с форматом файла даже если вы точно знаете, что сейчас они одинаковы; вы все равно пишете конверсии, так что в будущем ваш код будет работать без изменений на процессоре с противоположным порядком байтов.

вы можете сделать жизнь проще для себя, используя фиксированную ширину <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-количество бит осталось в качестве упражнения.


я предполагаю, что это вопрос прямой. т. е. вы ставите байт 42 и 4D в своем short значение. Но ваша система немного endian (у меня может быть неправильное имя), которая фактически читает байты (в многобайтовом целочисленном типе) слева направо, а не справа налево.

продемонстрировано в этом коде:

#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]

так если вы хотите быть портативны, то вам будет нужно обнаружить endian-ness ваша система, а затем выполните перетасовку байтов, если это необходимо. В интернете будет много примеров обмена байтами вокруг.

следующий вопрос:

я спрашиваю только потому, что мой размер файла 3 вместо 196662

это связано с проблемами выравнивания памяти. 196662-это байты 36 00 03 00 и 3-это байт 03 00 00 00. Большинству систем нужны такие типы, как int etc, чтобы не быть разделены на несколько памяти words. Так интуитивно вы думаете, что ваша структура выложена в памяти im, как:

                          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 as 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 собирается в вашу область заполнения, которая оставляет ваш file_size как получение 03 00 00 00. Теперь, если вы использовали fwrite чтобы создать эти данные, это должно было быть нормально, так как байты заполнения были бы выписаны. Но если ваш ввод всегда будет в указанном вами формате, нецелесообразно читать всю структуру как одну с fread. Вместо этого вам нужно будет прочитать каждый из элементов по отдельности.


запись структуры в файл очень непереносима - безопаснее всего просто не пытаться сделать это вообще. Использование такой структуры гарантированно работает только в том случае, если a) структура написана и прочитана как структура (никогда не последовательность байтов) и b) она всегда написана и прочитана на одном (типе) компьютере. Мало того, что есть "эндианские" проблемы с разными процессорами (что, похоже, вы столкнулись), есть также проблемы "выравнивания". Различные аппаратные реализации имеют разные правила размещения чисел только на даже 2 байта или даже 4 байта или даже 8-байтовые границы. Компилятор полностью осознает все это и вставляет скрытые байты заполнения в вашу структуру, чтобы она всегда работала правильно. Но в результате скрытых байтов заполнения небезопасно предполагать, что байты структуры выложены в памяти, как вы думаете. Если вам очень повезет, вы работаете на компьютере, который использует байтовый порядок big-endian и не имеет никаких ограничений выравнивания, поэтому вы можете заложить структуры непосредственно над файлами и заставить его работать. Но вам, вероятно, не так повезло-конечно программы, которые должны быть "портативными" для разных машин, должны избегать попыток укладывать структуры непосредственно над любой частью любого файла.