web-dev-qa-db-ja.com

バイナリファイルの解析。現代的な方法とは何ですか?

レイアウトがわかっているバイナリファイルがあります。たとえば、フォーマットを次のようにします。

  • 2バイト(unsigned short)-文字列の長さ
  • 5バイト(5 x文字)-文字列-ID名
  • 4バイト(unsigned int)-ストライド
  • 24バイト(6 xフロート-それぞれ3フロートの2ストライド)-フロートデータ

ファイルは次のようになります(読みやすくするためにスペースを追加しました)。

_5 hello 3 0.0 0.1 0.2 -0.3 -0.4 -0.5
_

ここで5-2バイトです:0x05 0x00。 「hello」-5バイトなど。

今、私はこのファイルを読みたいです。現在私はそうしています:

  • ファイルをifstreamにロードします
  • このストリームを_char buffer[2]_に読み取ります
  • それをunsigned shortにキャストします:unsigned short len{ *((unsigned short*)buffer) };。今、私は文字列の長さを持っています。
  • _vector<char>_へのストリームを読み取り、このベクターから_std::string_を作成します。これで文字列IDが得られました。
  • 同じ方法で次の4バイトを読み取り、それらをunsigned intにキャストします。今、私は大躍進しています。
  • ファイルの終わりではなく、フロートは同じ方法でフロートします-すべてのフロートに対して_char bufferFloat[4]_を作成し、*((float*)bufferFloat)をキャストします。

これは機能しますが、私にとっては見苦しいです。 _unsigned short_を作成せずに、_char [x]_またはfloatまたはstringなどに直接読み取ることはできますか?いいえの場合、正しくキャストする方法は何ですか(私が使用しているスタイルを読んでいます-古いスタイルです)?

P.S .:質問を書いている間、頭の中でもっと明確な説明が出てきました-_char [x]_の任意の位置から任意のバイト数をキャストする方法は?

更新:文字列と浮動小数点データの長さはコンパイル時に不明であり、可変であることを明示的に言及するのを忘れました。

46
nikitablack

C++では問題なく動作するCの方法は、構造体を宣言することです。

#pragma pack(1)

struct contents {
   // data members;
};

ご了承ください

  • プラグマを使用して、コンパイラーに構造体のデータas-it-looksを整列させる必要があります。
  • この手法は PODタイプ でのみ機能します

次に、読み取りバッファを構造体型に直接キャストします。

std::vector<char> buf(sizeof(contents));
file.read(buf.data(), buf.size());
contents *stuff = reinterpret_cast<contents *>(buf.data());

データのサイズが可変の場合、いくつかのチャンクに分割できます。バッファから単一のバイナリオブジェクトを読み取るには、リーダー関数が便利です。

template<typename T>
const char *read_object(const char *buffer, T& target) {
    target = *reinterpret_cast<const T*>(buffer);
    return buffer + sizeof(T);
}

主な利点は、そのようなリーダーをより高度なc ++オブジェクトに特化できることです。

template<typename CT>
const char *read_object(const char *buffer, std::vector<CT>& target) {
    size_t size = target.size();
    CT const *buf_start = reinterpret_cast<const CT*>(buffer);
    std::copy(buf_start, buf_start + size, target.begin());
    return buffer + size * sizeof(CT);
}

そして今、あなたのメインパーサーで:

int n_floats;
iter = read_object(iter, n_floats);
std::vector<float> my_floats(n_floats);
iter = read_object(iter, my_floats);

注:トニーDが観察したように、#pragmaディレクティブと手動パディング(必要な場合)でも、(最適な場合)パフォーマンスの問題や(最悪の場合)トラップ信号の形で、プロセッサのアライメントとの非互換性が発生する場合があります。この方法は、ファイルの形式を制御できる場合にのみ、おそらく興味深いものです。

9
slaphappy

学習目的ではなく、バイナリ形式を自由に選択できる場合は、protobufなどの使用を検討することをお勧めしますシリアル化を処理し、他のプラットフォームや言語と相互運用できるようにします。

サードパーティのAPIを使用できない場合は、QDataStreamを参考にしてください。

13
fjardon

現在私はそうしています:

  • ファイルをifstreamに読み込む

  • このストリームをcharバッファーに読み取ります[2]

  • unsigned shortunsigned short len{ *((unsigned short*)buffer) };にキャストします。今、私は文字列の長さを持っています。

最後にSIGBUS(文字配列が奇数アドレスで始まり、CPUが偶数アドレスに整列された16ビット値しか読み取れない場合)、パフォーマンス(一部のCPUは誤った値を読み取るが、より遅い場合) ;最新のx86のような他のものは、素晴らしく高速です)および/または エンディアン の問題です。 2つの文字を読むことをお勧めします。エンディアンを修正する必要がある場合は、 htons を使用して(x[0] << 8) | x[1]またはその逆を言うことができます。

  • vector<char>へのストリームを読み取り、このvectorからstd::stringを作成します。これで文字列IDが得られました。

必要ありません...文字列を直接読み込むだけです:

std::string s(the_size, ' ');

if (input_fstream.read(&s[0], s.size()) &&
    input_stream.gcount() == s.size())
    ...use s...
  • 同じ方法read次の4バイトをunsigned intにキャストします。今、私は大躍進しています。 whileファイルの終わりではないreadfloats同じ方法-char bufferFloat[4]を作成し、floatごとに*((float*)bufferFloat)をキャストします。

unsigned intsおよびfloatsを介してデータを直接読み取る方が良いでしょう。これにより、コンパイラーは正しいアライメントを保証します。

これは機能しますが、私にとっては見苦しいです。 unsigned shortを作成せずに、char [x]またはfloatまたはstringなどに直接読み取ることはできますか?いいえの場合、正しくキャストする方法は何ですか(私が使用しているスタイルを読んでいます-古いスタイルです)?

struct Data
{
    uint32_t x;
    float y[6];
};
Data data;
if (input_stream.read((char*)&data, sizeof data) &&
    input_stream.gcount() == sizeof data)
    ...use x and y...

上記のコードは、アライメントされていない可能性のあるchar配列(reinterpret_cast内を含む)内のstd::stringデータに対して安全でないため、アライメントされていない可能性のある文字配列へのデータの読み取りを回避します。繰り返しますが、ファイルの内容のエンディアンが異なる可能性がある場合は、htonlを使用して読み取り後の変換が必要になる場合があります。 floatsの数が不明な場合は、少なくとも4バイトのアライメントで十分なストレージを計算して割り当てる必要があります。その後、Data*を目指します。アクセスされたアドレスのメモリ内容が割り当ての一部であり、ストリームから読み込まれた有効なy表現を保持している限り、floatの配列サイズ。よりシンプル-読み取りが非常に遅くなる可能性があります-最初にuint32_tを読み取り、次にnew float[n]を読み取り、そこにさらにreadを実行します。

実際には、このタイプのアプローチは機能し、多くの低レベルおよびCコードがまさにこれを行います。ファイルを読むのに役立つかもしれない「よりクリーンな」高レベルのライブラリは、最終的には内部的に同様のことをしている必要があります。

9
Tony Delroy

先月、WikiWikiの形式の説明に従って.Zipファイルを読み取るために、高速でダーティなバイナリ形式のパーサーを実際に実装しました。

一部の特定のプラットフォームでは、パックされたstructが機能しますが、可変長のフィールドなど、うまく処理できないものがあります。ただし、テンプレートを使用すると、このような問題は発生しません。任意の複雑な構造(および戻り値の型)を取得できます。

.Zipアーカイブは比較的簡単で、幸いなことに、私は簡単なものを実装しました。私の頭の上から:

using Buffer = std::pair<unsigned char const*, size_t>;

template <typename OffsetReader>
class UInt16LEReader: private OffsetReader {
public:
    UInt16LEReader() {}
    explicit UInt16LEReader(OffsetReader const or): OffsetReader(or) {}

    uint16_t read(Buffer const& buffer) const {
        OffsetReader const& or = *this;

        size_t const offset = or.read(buffer);
        assert(offset <= buffer.second && "Incorrect offset");
        assert(offset + 2 <= buffer.second && "Too short buffer");

        unsigned char const* begin = buffer.first + offset;

        // http://commandcenter.blogspot.fr/2012/04/byte-order-fallacy.html
        return (uint16_t(begin[0]) << 0)
             + (uint16_t(begin[1]) << 8);
    }
}; // class UInt16LEReader

// Declined for UInt[8|16|32][LE|BE]...

もちろん、基本的なOffsetReaderは実際には一定の結果になります。

template <size_t O>
class FixedOffsetReader {
public:
    size_t read(Buffer const&) const { return O; }
}; // class FixedOffsetReader

また、テンプレートについて話しているので、自由にタイプを切り替えることができます(すべての読み取りをそれらをメモするshared_ptrに委任するプロキシリーダーを実装できます)。

しかし、興味深いのは最終結果です。

// http://en.wikipedia.org/wiki/Zip_%28file_format%29#File_headers
class LocalFileHeader {
public:
    template <size_t O>
    using UInt32 = UInt32LEReader<FixedOffsetReader<O>>;
    template <size_t O>
    using UInt16 = UInt16LEReader<FixedOffsetReader<O>>;

    UInt32< 0> signature;
    UInt16< 4> versionNeededToExtract;
    UInt16< 6> generalPurposeBitFlag;
    UInt16< 8> compressionMethod;
    UInt16<10> fileLastModificationTime;
    UInt16<12> fileLastModificationDate;
    UInt32<14> crc32;
    UInt32<18> compressedSize;
    UInt32<22> uncompressedSize;

    using FileNameLength = UInt16<26>;
    using ExtraFieldLength = UInt16<28>;

    using FileName = StringReader<FixedOffsetReader<30>, FileNameLength>;

    using ExtraField = StringReader<
        CombinedAdd<FixedOffsetReader<30>, FileNameLength>,
        ExtraFieldLength
    >;

    FileName filename;
    ExtraField extraField;
}; // class LocalFileHeader

これはかなり単純ですが、明らかに、しかし非常に柔軟です。

明らかな改善の軸は、連鎖を改善することです。ここでは、偶発的な重複のリスクがあるためです。私のアーカイブ読み取りコードは、最初に試してみたときに機能しました。このコードは、当面のタスクに十分であるという証拠でした。

7
Matthieu M.

この問題を一度解決しなければなりませんでした。データファイルは、FORTRAN出力で圧縮されています。アライメントはすべて間違っていました。私はあなたが手動でやっていることを自動的に行うプリプロセッサのトリックで成功しました:生データをバイトバッファから構造体に解凍します。考え方は、インクルードファイルでデータを記述することです。

BEGIN_STRUCT(foo)
    UNSIGNED_SHORT(length)
    STRING_FIELD(length, label)
    UNSIGNED_INT(stride)
    FLOAT_ARRAY(3 * stride)
END_STRUCT(foo)

これで、これらのマクロを定義して必要なコード、たとえばstruct宣言を生成し、上記を含め、undefし、マクロを再度定義してアンパック関数を生成し、その後に別のincludeなどを作成できます。

NB私は最初に、gccで抽象構文ツリー関連のコード生成のためにこの手法が使用されるのを見ました。

CPPが十分に強力でない場合(または、そのようなプリプロセッサの乱用はあなたには向かない)、小さなLex/yaccプログラムに置き換えます(または、お気に入りのツールを選択します)。

少なくともこのような低レベルの基礎コードでは、コードを手で書くのではなく、コードを生成するという観点で考えるのがどれほどの頻度で支払うかは、私にとって驚くべきことです。

3
Gene

構造体をより適切に宣言する必要があります(1バイトのパディング-how-はコンパイラーに依存します)。その構造を使用して書き込み、同じ構造を使用して読み取ります。構造にPODのみを入れ、したがってstd::stringなど。この構造は、ファイルI/Oまたは他のプロセス間通信にのみ使用します。通常のstructまたはclassを使用して、C++プログラムでさらに使用できるようにします。

2
Ajay

すべてのデータは可変であるため、2つのブロックを別々に読み取り、キャストを使用できます。

struct id_contents
{
    uint16_t len;
    char id[];
} __attribute__((packed)); // assuming gcc, ymmv

struct data_contents
{
    uint32_t stride;
    float data[];
} __attribute__((packed)); // assuming gcc, ymmv

class my_row
{
    const id_contents* id_;
    const data_contents* data_;
    size_t len;

public:
    my_row(const char* buffer) {
        id_= reinterpret_cast<const id_contents*>(buffer);
        size_ = sizeof(*id_) + id_->len;
        data_ = reinterpret_cast<const data_contents*>(buffer + size_);
        size_ += sizeof(*data_) + 
            data_->stride * sizeof(float); // or however many, 3*float?

    }

    size_t size() const { return size_; }
};

そうすれば、kbok氏の回答を使用して正しく解析できます。

const char* buffer = getPointerToDataSomehow();

my_row data1(buffer);
buffer += data1.size();

my_row data2(buffer);
buffer += data2.size();

// etc.
2
Barry

私は個人的に次のようにします:

// some code which loads the file in memory
#pragma pack(Push, 1)
struct someFile { int a, b, c; char d[0xEF]; };
#pragma pack(pop)

someFile* f = (someFile*) (file_in_memory);
int filePropertyA = f->a;

ファイルの先頭にある固定サイズの構造体の非常に効果的な方法。

2
rev

シリアル化ライブラリを使用します。以下にいくつかを示します。

1
Átila Neves

ragelツールを使用して、RAMが1-2Kのマイクロコントローラー用の純粋なC手続き型ソースコード(テーブルなし)を生成します。ファイルioやバッファリングは使用せず、デバッグしやすいコードとステートマシン図を含む.dot/.pdfファイルの両方を生成します。

ragelはgo、Java、..解析用のコードも出力できますが、これらの機能は使用しませんでした。

ragelの重要な機能は、バイトビルドデータを解析する機能ですが、ビットフィールドに掘り下げることはできません。その他の問題は、正規の構造を解析できるragelですが、再帰および構文文法解析はありません。

0
Dmitry Ponyatov