Не читайте напрямую в структуру из файла! Упаковка может быть другой, вы должны возиться с пакетом pragma или аналогичными конструкциями, специфичными для компилятора. Слишком ненадежно. Многим программистам это сходит с рук, поскольку их код не скомпилирован в большом количестве архитектур и систем, но это не значит, что это нормально!
Хороший альтернативный подход - это прочитать заголовок, в любом случае, в буфер и проанализировать три, чтобы избежать издержек ввода-вывода в элементарных операциях, таких как чтение 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..
Легко переключать порядковый номер для одного и того же кода, однако, редко необходимая функция. В любом случае форматы файлов обычно имеют фиксированный порядковый номер.
НЕ абстрагируйте это в класс с помощью виртуальных методов; просто добавит накладные расходы, но не стесняйтесь, если так склонны:
little_endian_reader reader(data, size);
uint32 x = reader.read_uint32();
uint32 y = reader.read_uint32();
Объект читателя, очевидно, будет просто тонкой оболочкой вокруг указателя. Параметр размера будет для проверки ошибок, если таковые имеются. Не совсем обязательно для интерфейса как такового.
Обратите внимание, как выбор порядка байтов здесь был сделан во время компиляции (так как мы создаем объект little_endian_reader), поэтому мы вызываем издержки виртуального метода без особой веской причины, поэтому я бы не стал использовать этот подход. ; -)
На этом этапе нет реальной причины сохранять «структуру файлового формата» как есть, вы можете организовать данные по своему вкусу и вовсе не обязательно читать их в какой-либо конкретной структуре; в конце концов, это просто данные. Когда вы читаете файлы, например изображения, вам не нужен заголовок вокруг ... у вас должен быть контейнер изображений, который одинаков для всех типов файлов, поэтому код для чтения определенного формата должен просто читать файл, интерпретировать и переформатировать данные и хранить полезную нагрузку. =) * * 1 029
Я имею в виду, это выглядит сложно?
uint32 xsize = buffer.read<uint32>();
uint32 ysize = buffer.read<uint32>();
float aspect = buffer.read<float>();
Код может выглядеть так красиво и очень экономно! Если порядок байтов одинаков для файла и архитектуры, для которой компилируется код, innerloop может выглядеть следующим образом:
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» в промежуточный код, ничего более, что довольно эффективно. Если выравнивание является проблемой, может быть сгенерирована полная последовательность чтения-смены или нет, но все же не слишком потертая. Ветвь сравнения может позволить оптимизацию, если протестировать адрес LSB и посмотреть, можно ли использовать быструю или медленную версию синтаксического анализа. Но это будет означать штраф за тест в каждом чтении. Не стоило бы усилий.
О, верно, мы читаем заголовки и прочее, я не думаю, что это является узким местом во многих приложениях. Если какой-то кодек выполняет действительно НАСТОЯЩУЮ внутреннюю петлю, опять же рекомендуется чтение во временный буфер и декодирование оттуда. Тот же принцип .. никто не читает по байтам из файла при обработке большого объема данных. Ну, на самом деле, я видел такой код очень часто, и обычный ответ на вопрос «почему вы это делаете» заключается в том, что файловые системы выполняют блокировку чтения и что байты все равно приходят из памяти, правда, но они проходят через глубокий стек вызовов что требует много байтов!
Тем не менее, напишите код парсера один раз и используйте миллионы раз -> эпический выигрыш.
Чтение непосредственно в struct из файла: НЕ ДЕЛАЙТЕ НАРОДОВ!