レイアウトがわかっているバイナリファイルがあります。たとえば、フォーマットを次のようにします。
ファイルは次のようになります(読みやすくするためにスペースを追加しました)。
_5 hello 3 0.0 0.1 0.2 -0.3 -0.4 -0.5
_
ここで5-2バイトです:0x05 0x00。 「hello」-5バイトなど。
今、私はこのファイルを読みたいです。現在私はそうしています:
char buffer[2]
_に読み取りますunsigned short len{ *((unsigned short*)buffer) };
。今、私は文字列の長さを持っています。vector<char>
_へのストリームを読み取り、このベクターから_std::string
_を作成します。これで文字列IDが得られました。char bufferFloat[4]
_を作成し、*((float*)bufferFloat)
をキャストします。これは機能しますが、私にとっては見苦しいです。 _unsigned short
_を作成せずに、_char [x]
_またはfloat
またはstring
などに直接読み取ることはできますか?いいえの場合、正しくキャストする方法は何ですか(私が使用しているスタイルを読んでいます-古いスタイルです)?
P.S .:質問を書いている間、頭の中でもっと明確な説明が出てきました-_char [x]
_の任意の位置から任意のバイト数をキャストする方法は?
更新:文字列と浮動小数点データの長さはコンパイル時に不明であり、可変であることを明示的に言及するのを忘れました。
C++では問題なく動作するCの方法は、構造体を宣言することです。
#pragma pack(1)
struct contents {
// data members;
};
ご了承ください
次に、読み取りバッファを構造体型に直接キャストします。
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
ディレクティブと手動パディング(必要な場合)でも、(最適な場合)パフォーマンスの問題や(最悪の場合)トラップ信号の形で、プロセッサのアライメントとの非互換性が発生する場合があります。この方法は、ファイルの形式を制御できる場合にのみ、おそらく興味深いものです。
現在私はそうしています:
ファイルをifstreamに読み込む
このストリームをcharバッファーに読み取ります[2]
unsigned short
:unsigned 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
ファイルの終わりではないread
float
s同じ方法-char bufferFloat[4]
を作成し、float
ごとに*((float*)bufferFloat)
をキャストします。
unsigned int
sおよび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
を使用して読み取り後の変換が必要になる場合があります。 float
sの数が不明な場合は、少なくとも4バイトのアライメントで十分なストレージを計算して割り当てる必要があります。その後、Data*
を目指します。アクセスされたアドレスのメモリ内容が割り当ての一部であり、ストリームから読み込まれた有効なy
表現を保持している限り、float
の配列サイズ。よりシンプル-読み取りが非常に遅くなる可能性があります-最初にuint32_t
を読み取り、次にnew float[n]
を読み取り、そこにさらにread
を実行します。
実際には、このタイプのアプローチは機能し、多くの低レベルおよびCコードがまさにこれを行います。ファイルを読むのに役立つかもしれない「よりクリーンな」高レベルのライブラリは、最終的には内部的に同様のことをしている必要があります。
先月、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
これはかなり単純ですが、明らかに、しかし非常に柔軟です。
明らかな改善の軸は、連鎖を改善することです。ここでは、偶発的な重複のリスクがあるためです。私のアーカイブ読み取りコードは、最初に試してみたときに機能しました。このコードは、当面のタスクに十分であるという証拠でした。
この問題を一度解決しなければなりませんでした。データファイルは、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プログラムに置き換えます(または、お気に入りのツールを選択します)。
少なくともこのような低レベルの基礎コードでは、コードを手で書くのではなく、コードを生成するという観点で考えるのがどれほどの頻度で支払うかは、私にとって驚くべきことです。
構造体をより適切に宣言する必要があります(1バイトのパディング-how-はコンパイラーに依存します)。その構造を使用して書き込み、同じ構造を使用して読み取ります。構造にPODのみを入れ、したがってstd::string
など。この構造は、ファイルI/Oまたは他のプロセス間通信にのみ使用します。通常のstruct
またはclass
を使用して、C++プログラムでさらに使用できるようにします。
すべてのデータは可変であるため、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.
私は個人的に次のようにします:
// 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;
ファイルの先頭にある固定サイズの構造体の非常に効果的な方法。
シリアル化ライブラリを使用します。以下にいくつかを示します。
ragel
ツールを使用して、RAMが1-2Kのマイクロコントローラー用の純粋なC手続き型ソースコード(テーブルなし)を生成します。ファイルioやバッファリングは使用せず、デバッグしやすいコードとステートマシン図を含む.dot/.pdfファイルの両方を生成します。
ragelはgo、Java、..解析用のコードも出力できますが、これらの機能は使用しませんでした。
ragel
の重要な機能は、バイトビルドデータを解析する機能ですが、ビットフィールドに掘り下げることはできません。その他の問題は、正規の構造を解析できるragelですが、再帰および構文文法解析はありません。