ファイルから構造体に直接読み込まないでください。パッキングは異なる場合があります。プラグマパックまたは同様のコンパイラ固有の構造をいじる必要があります。信頼性が低すぎます。多くのプログラマーは、コードが多数のアーキテクチャーやシステムでコンパイルされていないため、これを回避しますが、それはそれが問題ないという意味ではありません。
良い代替アプローチは、ヘッダーをバッファに読み込み、3から解析して、符号なし32ビット整数の読み取りなどのアトミック操作でのI/Oオーバーヘッドを回避することです。
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オブジェクトを作成したため)。したがって、特に正当な理由なしに仮想メソッドのオーバーヘッドを呼び出すため、このアプローチは使用しません。;-)
この段階では、「fileformat構造体」をそのままにしておく本当の理由はありません。データを好みに合わせて整理でき、必ずしも特定の構造体に読み込む必要はありません。結局のところ、それは単なるデータです。画像のようなファイルを読み取る場合、実際にはヘッダーは必要ありません。すべてのファイルタイプで同じ画像コンテナが必要です。したがって、特定の形式を読み取るコードは、ファイルを読み取り、解釈して再フォーマットするだけです。データとペイロードを保存します。=)
つまり、これは複雑に見えますか?
uint32 xsize = buffer.read<uint32>();
uint32 ysize = buffer.read<uint32>();
float aspect = buffer.read<float>();
コードは見栄えがよく、オーバーヘッドが非常に低くなります。コードがコンパイルされるファイルとアーキテクチャのエンディアンが同じである場合、内部ループは次のようになります。
uint32 value = *reinterpret_cast<uint32*>)(ptr); ptr += 4;
return value;
これは一部のアーキテクチャでは違法である可能性があるため、最適化は悪い考えであり、低速ですがより堅牢なアプローチを使用する可能性があります。
uint32 value = ptr[0] | (static_cast<uint32>(ptr[1]) << 8) | ...; ptr += 4;
return value;
bswapまたはmovにコンパイルできるx86では、メソッドがインライン化されている場合、オーバーヘッドはかなり低くなります。コンパイラは「move」ノードを中間コードに挿入しますが、これはかなり効率的です。アラインメントが問題である場合、完全な読み取りシフトまたはシーケンスが生成される可能性がありますが、それでもそれほど粗末ではありません。Compare-branchは、アドレスLSBをテストし、解析の高速バージョンまたは低速バージョンを使用できるかどうかを確認する場合に、最適化を可能にする可能性があります。しかし、これはすべての読み取りでのテストのペナルティを意味します。努力する価値がないかもしれません。
ああ、そうです、私たちはHEADERSなどを読んでいますが、それがあまりにも多くのアプリケーションのボトルネックであるとは思いません。一部のコーデックが本当にタイトな内部ループを実行している場合も、一時バッファに読み込んでそこからデコードすることをお勧めします。同じ原則..大量のデータを処理するときに、ファイルから一度にバイトを読み取る人は誰もいません。実際、私はその種のコードを頻繁に見ました。「なぜそれを行うのか」に対する通常の応答は、ファイルシステムがブロック読み取りを実行し、バイトはとにかくメモリから取得されるというものですが、それらは深い呼び出しスタックを通過しますこれは、数バイトを取得するためのオーバーヘッドが高くなります。
それでも、パーサーコードを1回記述し、無数の回数を使用します->壮大な勝利。
ファイルから構造体を直接読み取る:Folksを実行しないでください!