Чтение двоичного файла, определенного структурой - PullRequest
3 голосов
/ 15 мая 2009

Может ли кто-нибудь указать мне правильное направление, как я мог бы прочитать двоичный файл, который определяется структурой C? Внутри структуры есть несколько #define, что заставляет меня думать, что это усложнит ситуацию.
Структура выглядит примерно так: (хотя она больше и сложнее, чем эта)

struct Format {
    unsigned long str_totalstrings;
    unsigned long str_name;
    #define STR_ORDERED 0x2
    #define STR_ROT13 0x4
    unsigned char stuff[4];
    #define str_delimiter stuff[0]
}

Я был бы очень признателен, если бы кто-нибудь указал мне правильное направление, как это сделать. Или, если есть какой-то учебник, который охватывает эту тему?

Заранее большое спасибо за помощь.

Ответы [ 5 ]

5 голосов
/ 02 октября 2016

Есть плохие идеи и хорошие идеи:

Это плохая идея:

  • Введите необработанный буфер в структуру
    • Имеются проблемы с порядком байтов (с прямым порядком байтов и с прямым порядком байтов) при разборе целых чисел> 1 байта или чисел с плавающей запятой
    • Есть проблемы с выравниванием байтов в структурах, которые очень зависят от компилятора. Можно попытаться отключить выравнивание (или принудительно настроить выравнивание вручную), но в целом это тоже плохая идея. По крайней мере, вы снизите производительность, сделав доступ к процессору невыровненными целыми числами. Внутреннее ядро ​​RISC должно было бы выполнить 3-4 операции вместо 1 (то есть «выполнить часть 1 в первом слове», «выполнить часть 2 во втором слове», «объединить результат») для доступа к нему каждый раз. Или, что еще хуже, прагмы компилятора, управляющие выравниванием, будут игнорироваться, и ваш код будет поврежден.
    • Нет точных гарантий размера для обычного int, long, short и т. Д., Типа C / C ++. Вы можете использовать такие вещи, как int16_t, но они доступны только на современных компиляторах.
    • Конечно, этот подход полностью нарушается при использовании структур, которые ссылаются на другие структуры: нужно развернуть их все вручную.
  • Писать парсеры вручную: это гораздо сложнее, чем кажется на первый взгляд.
    • Хороший парсер должен много проверять работоспособность на каждом этапе. Легко что-то пропустить. Еще проще что-то пропустить, если вы не используете исключения.
    • Использование исключений делает вас склонным к сбою, если ваш код синтаксического анализа не является безопасным для исключений (т. Е. Записан таким образом, что он может быть прерван в некоторых точках и не пропустит память / не забудет завершить некоторые объекты)
    • Могут быть проблемы с производительностью (т. Е. Выполнение большого количества небуферизованного ввода-вывода вместо выполнения одной системной read системного вызова и последующего синтаксического анализа буфера - или наоборот, одновременное чтение всего объекта вместо более детального, ленивого чтения, где это применимо) .

Это хорошая идея для

  • Перейти кросс-платформенный. В значительной степени говорят сами за себя, в последние годы все мобильные устройства, маршрутизаторы и IoT быстро развиваются.
  • Иди декларативно. Рассмотрите возможность использования любой декларативной спецификации для описания вашей структуры, а затем используйте генератор парсера для генерации парсера.

Для этого есть несколько инструментов:

  • Kaitai Struct - мой любимый до сих пор кросс-платформенный кросс-язык - т.е. вы описываете свою структуру один раз, а затем можете скомпилировать ее в синтаксический анализатор на C ++, C #, Java, Python, Ruby , PHP и т. Д.
  • binpac - довольно устаревший, но все еще пригодный для использования, только C ++ - похож на Kaitai по идеологии, но не поддерживается с 2013 года
  • Spicy - называется «современным переписыванием» binpac, AKA «binpac ++», но все еще находится на ранних стадиях разработки; может использоваться для небольших задач, только C ++.
4 голосов
/ 15 мая 2009

Чтение двоичного файла, определенного структурой, очень просто.

Format myFormat;
fread(&myFormat, sizeof(Format), 1, fp);

#defines вообще не влияют на структуру. (Внутри странное место, чтобы положить их, хотя).

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

Лучшим способом было бы переопределить вашу структуру следующим образом:

struct Format {
    Uint32 str_totalstrings;  //assuming unsigned long was 32 bits on the writer.
    Uint32 str_name;
    unsigned char stuff[4];
};

, а затем есть «platform_types.h», который правильно определяет Uint32 для вашего компилятора. Теперь вы можете читать непосредственно в структуру, но для проблем с порядком байтов вам все еще нужно сделать что-то вроде этого:

myFormat.str_totalstrings = FileToNative32(myFormat.str_totalstrings);
myFormat.str_name =   FileToNative32(str_name);

где FileToNative в зависимости от платформы является либо неработающим, либо байтовым реверсером.

2 голосов
/ 15 мая 2009

Использование библиотеки ввода-вывода C ++:

#include <fstream>
using namespace std;

ifstream ifs("file.dat", ios::binary);
Format f;
ifs.get(&f, sizeof f);

Использование библиотеки ввода-вывода C:

#include <cstdio>
using namespace std;

FILE *fin = fopen("file.dat", "rb");
Format f;
fread(&f, sizeof f, 1, fin);
2 голосов
/ 15 мая 2009

Вы также можете использовать союзы для этого анализа, если у вас есть данные, которые вы хотите проанализировать, уже в памяти.

union A {
    char* buffer;
    Format format;
};

A a;
a.buffer = stuff_you_want_to_parse;

// You can now access the members of the struct through the union.
if (a.format.str_name == "...")
    // do stuff

Также помните, что long может быть разных размеров на разных платформах. Если вы зависите от long определенного размера, рассмотрите возможность использования типов, определенных int stdint.h, таких как uint32_t.

0 голосов
/ 15 мая 2009

Вы должны узнать порядковые номера машины, на которой был записан файл, чтобы вы могли правильно интерпретировать целые числа. Обратите внимание на несоответствие ILP32 и LP64. Оригинальная структура упаковки / выравнивания также может быть важна.

...