Почему fread портит мой порядок байтов? - PullRequest
11 голосов
/ 19 декабря 2011

Я пытаюсь проанализировать файл 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 () делает это и как я могу это исправить;

РЕДАКТИРОВАТЬ: Если я не был достаточно конкретным, мне нужно прочитать всю структуру шестнадцатеричных данных в структуре, а не только магическое число. Я выбрал только магическое число в качестве примера.

Ответы [ 3 ]

15 голосов
/ 19 декабря 2011

Это не ошибка fread, а вашего процессора, который (очевидно) имеет порядковый номер. То есть ваш ЦП обрабатывает первый байт в значении 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> с фиксированной шириной, используя типы без знака, если только не требуется представлять отрицательные числа, а не , используя целые числа, когда массивы символов Сделаю. Я сделал все это в приведенном выше примере. Вы можете видеть, что вам не нужно беспокоиться о преобразовании магического числа в порядок байтов, потому что единственное, что вам нужно с ним сделать, это проверить 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 голосов
/ 19 декабря 2011

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

0 голосов
/ 31 августа 2012

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

...