Как преобразовать структуру большого конца в небольшую endian-struct?
У меня есть двоичный файл, который был создан на машине unix. Это просто куча записей, написанных один за другим. Запись определяется следующим образом:
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UNIT16 baz;
}
Я пытаюсь понять, как я буду читать и интерпретировать эти данные на машине Windows. У меня есть что-то вроде этого:
fstream f;
f.open("file.bin", ios::in | ios::binary);
RECORD r;
f.read((char*)&detail, sizeof(RECORD));
cout << "fooword = " << r.fooword << endl;
Я получаю кучу данных, но это не те данные, которые я ожидаю. Я подозреваю, что моя проблема связана с конечной разницей машин, поэтому я пришел спросить об этом.
Я понимаю, что несколько байтов будут храниться в little-endian на windows и big-endian в среде unix, и я получаю это. Для двух байтов 0x1234 на окнах будет 0x3412 в системе unix.
Является ли endianness влиять на порядок байтов структуры в целом или каждого отдельного члена структуры? Какие подходы я возьму, чтобы преобразовать структуру, созданную в системе unix, в ту, которая имеет одни и те же данные в системе Windows? Любые ссылки, которые являются более глубокими, чем порядок байтов пары байтов, тоже будут хороши!
Ответы
Ответ 1
Также как и endian, вам нужно знать разницу между двумя платформами. В частности, если у вас есть нечетные длины char массивы и 16-битные значения, вы можете найти различное количество байтов pad между некоторыми элементами.
Изменить: если структура была выписана без упаковки, то она должна быть довольно простой. Что-то вроде этого (непроверенного) кода должно выполнять следующее задание:
// Functions to swap the endian of 16 and 32 bit values
inline void SwapEndian(UINT16 &val)
{
val = (val<<8) | (val>>8);
}
inline void SwapEndian(UINT32 &val)
{
val = (val<<24) | ((val<<8) & 0x00ff0000) |
((val>>8) & 0x0000ff00) | (val>>24);
}
Затем, как только вы загрузите структуру, просто поменяйте каждый элемент:
SwapEndian(r.foo);
SwapEndian(r.bar);
SwapEndian(r.baz);
Ответ 2
На самом деле, endianness является свойством базового оборудования, а не ОС.
Лучшим решением является преобразование в стандарт при записи данных - Google для "байтового байта сети", и вы должны найти методы для этого.
Изменить: здесь ссылка: http://www.gnu.org/software/hello/manual/libc/Byte-Order.html
Ответ 3
Не читать непосредственно в struct из файла! Упаковка может отличаться, вы должны возиться с пакетами pragma pack или аналогичными конструкциями компилятора. Слишком ненадежный. Многим программистам это удается, поскольку их код не скомпилирован в большом количестве архитектур и систем, но это не значит, что это нужно делать!
Хорошим альтернативным подходом является чтение заголовка, что угодно, в буфер и синтаксический анализ из трех, чтобы избежать накладных расходов ввода-вывода в атомных операциях, таких как чтение 32-битного целого числа без знака!
char buffer[32];
char* temp = buffer;
f.read(buffer, 32);
RECORD rec;
rec.foo = parse_uint32(temp); temp += 4;
rec.bar = parse_uint32(temp); temp += 4;
memcpy(&rec.fooword, temp, 11); temp += 11;
memcpy(%red.barword, temp, 11); temp += 11;
rec.baz = parse_uint16(temp); temp += 2;
Объявление parse_uint32 будет выглядеть так:
uint32 parse_uint32(char* buffer)
{
uint32 x;
// ...
return x;
}
Это очень простая абстракция, на практике также не нужно обновлять указатель:
uint32 parse_uint32(char*& buffer)
{
uint32 x;
// ...
buffer += 4;
return x;
}
Более поздняя форма позволяет использовать более чистый код для разбора буфера; указатель автоматически обновляется при анализе с входа.
Аналогично, memcpy может иметь помощника, что-то вроде:
void parse_copy(void* dest, char*& buffer, size_t size)
{
memcpy(dest, buffer, size);
buffer += size;
}
Красота такого устройства заключается в том, что вы можете иметь пространство имен "little_endian" и "big_endian", тогда вы можете сделать это в своем коде:
using little_endian;
// do your parsing for little_endian input stream here..
Простота переключения endianess для одного и того же кода, хотя и редко нужна функция. В любом случае файловые форматы обычно имеют фиксированную endianess.
НЕ абстрагируйте это в класс с помощью виртуальных методов; просто добавит накладные расходы, но не стесняйтесь, если это так:
little_endian_reader reader(data, size);
uint32 x = reader.read_uint32();
uint32 y = reader.read_uint32();
Объект считывателя, очевидно, будет просто тонкой оболочкой вокруг указателя. Параметр размера будет для проверки ошибок, если таковой имеется. Не обязательно обязательно для интерфейса.
Обратите внимание на то, как здесь был выбран выбор endianess в момент COMPILATION TIME (поскольку мы создаем объект little_endian_reader), поэтому мы вызываем накладные расходы виртуального метода без особых оснований, поэтому я бы не пошел с таким подходом.; -)
На этом этапе нет реальной причины поддерживать структуру "fileformat struct" как-есть, вы можете упорядочить данные по своему вкусу и не обязательно читать их в какой-либо конкретной структуре; в конце концов, это просто данные. Когда вы читаете такие файлы, как изображения, вам не нужен заголовок вокруг. У вас должен быть контейнер изображений, который одинаковый для всех типов файлов, поэтому код для чтения определенного формата должен просто читать файл, интерпретировать и переформатировать данных и хранить полезную нагрузку. =)
Я имею в виду, сложно ли это выглядеть?
uint32 xsize = buffer.read<uint32>();
uint32 ysize = buffer.read<uint32>();
float aspect = buffer.read<float>();
Код может выглядеть так красиво и быть очень низким накладные расходы! Если endianess одинакова для файла и архитектуры, код скомпилирован, внутренняя точка может выглядеть так:
uint32 value = *reinterpret_cast<uint32*>)(ptr); ptr += 4;
return value;
Это может быть незаконным на некоторых архитектурах, поэтому оптимизация может быть плохой идеей и использовать более медленный, но более надежный подход:
uint32 value = ptr[0] | (static_cast<uint32>(ptr[1]) << 8) | ...; ptr += 4;
return value;
На x86, который может компилироваться в bswap или mov, что является разумно низким, если метод встроен; компилятор будет вставлять "move" node в промежуточный код, ничего более, что довольно эффективно. Если выравнивание является проблемой, полный сдвиг чтения или последовательность может получить сгенерированный, outch, но все же не слишком потрепанный. Compare-branch может позволить оптимизацию, если проверить адрес LSB и посмотреть, может ли использовать быструю или медленную версию синтаксического анализа. Но это будет означать штраф за тест в каждом чтении. Может не стоить усилий.
О, верно, мы читаем HEADERS и прочее, я не думаю, что это узкое место в слишком многих приложениях. Если какой-то кодек действительно делает ТОЛЬКО внутреннюю внутреннюю оболочку, опять же, чтение во временном буфере и декодирование оттуда хорошо обоснованы. Тот же принцип.. никто не читает байт по времени из файла при обработке большого объема данных. Ну, на самом деле, я видел такой код очень часто, и обычный ответ на "почему вы это делаете" заключается в том, что файловые системы выполняют блочные чтения и что байты поступают из памяти в любом случае, правда, но они проходят через стек глубоких вызовов который является большим накладным для получения нескольких байтов!
И все же, напишите код парсера один раз и используйте zillion раз → эпическая победа.
Считывание непосредственно в структуру из файла: НЕ СДЕЛАЙТЕ ЭТО СФЕРЫ!
Ответ 4
Он влияет на каждого члена независимо, а не на весь struct
. Кроме того, это не влияет на такие вещи, как массивы. Например, он просто делает байты в int
сохраненными в обратном порядке.
PS. Тем не менее, может быть машина со странным энтузиазмом. То, что я только что сказал, относится к большинству используемых машин (x86, ARM, PowerPC, SPARC).
Ответ 5
Вы должны исправить утверждение каждого члена более одного байта, индивидуально. Строки не нужно преобразовывать (fooword и barword), поскольку их можно рассматривать как последовательности байтов.
Однако вы должны позаботиться о другой проблеме: aligmenent членов вашей структуры. В принципе, вы должны проверить, является ли sizeof (RECORD) одинаковым как для unix, так и для кода Windows. Компиляторы обычно предоставляют прагмы для определения нужного вам элемента (например, #pragma pack).
Ответ 6
Вы также должны учитывать различия в выравнивании между двумя компиляторами. Каждому компилятору разрешено вставлять дополнения между членами в структуре, наиболее подходящей для архитектуры. Поэтому вам действительно нужно знать:
- Как прокси файл UNIX записывает в файл
- Если это двоичная копия объекта, то точное расположение структуры.
- Если это двоичная копия, то какой смысл исходной архитектуры.
Вот почему большинство программ (которые я видел (которые должны быть нейтральными для платформы)) сериализуют данные как текстовый поток, который можно легко прочитать стандартными iostreams.
Ответ 7
Мне нравится внедрять метод SwapBytes для каждого типа данных, который нуждается в замене, например:
inline u_int ByteSwap(u_int in)
{
u_int out;
char *indata = (char *)∈
char *outdata = (char *)&out;
outdata[0] = indata[3] ;
outdata[3] = indata[0] ;
outdata[1] = indata[2] ;
outdata[2] = indata[1] ;
return out;
}
inline u_short ByteSwap(u_short in)
{
u_short out;
char *indata = (char *)∈
char *outdata = (char *)&out;
outdata[0] = indata[1] ;
outdata[1] = indata[0] ;
return out;
}
Затем я добавляю функцию к структуре, которая нуждается в замене, например:
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UNIT16 baz;
void SwapBytes()
{
foo = ByteSwap(foo);
bar = ByteSwap(bar);
baz = ByteSwap(baz);
}
}
Затем вы можете изменить свой код, который читает (или записывает) структуру следующим образом:
fstream f;
f.open("file.bin", ios::in | ios::binary);
RECORD r;
f.read((char*)&detail, sizeof(RECORD));
r.SwapBytes();
cout << "fooword = " << r.fooword << endl;
Для поддержки разных платформ вам просто нужно реализовать определенную платформу для каждой перегрузки ByteSwap.
Ответ 8
Что-то вроде этого должно работать:
#include <algorithm>
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UINT16 baz;
}
void ReverseBytes( void *start, int size )
{
char *beg = start;
char *end = beg + size;
std::reverse( beg, end );
}
int main() {
fstream f;
f.open( "file.bin", ios::in | ios::binary );
// for each entry {
RECORD r;
f.read( (char *)&r, sizeof( RECORD ) );
ReverseBytes( r.foo, sizeof( UINT32 ) );
ReverseBytes( r.bar, sizeof( UINT32 ) );
ReverseBytes( r.baz, sizeof( UINT16 )
// }
return 0;
}