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