UNIXマシンで作成されたバイナリファイルがあります。それは次々に書かれたレコードの束です。レコードは次のように定義されます。
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UNIT16 baz;
}
私は、Windowsマシンでこのデータをどのように読み取って解釈するかを理解しようとしています。私はこのようなものを持っています:
fstream f;
f.open("file.bin", ios::in | ios::binary);
RECORD r;
f.read((char*)&detail, sizeof(RECORD));
cout << "fooword = " << r.fooword << endl;
たくさんのデータを取得しましたが、期待するデータではありません。私の問題はマシンのエンディアンの違いに関係しているのではないかと思うので、それについて尋ねるようになりました。
複数のバイトがWindowsではリトルエンディアンで格納され、UNIX環境ではビッグエンディアンで格納されることを理解しています。 2バイトの場合、Windowsの0x1234はUNIXシステムの0x3412になります。
エンディアンは、構造体全体のバイト順序に影響しますか、それとも構造体の個々のメンバーのバイト順序に影響しますか? UNIXシステムで作成された構造体をWindowsシステムで同じデータを持つ構造体に変換するにはどのようなアプローチが必要ですか?数バイトのバイトオーダーよりも深いリンクも素晴らしいでしょう!
エンディアンだけでなく、2つのプラットフォーム間のパディングの違いにも注意する必要があります。特に、奇数の長さのchar配列と16ビット値がある場合、いくつかの要素間で異なる数のパッドバイトが見つかる可能性があります。
編集:構造がパッキングなしで書き出された場合、それはかなり簡単なはずです。この(テストされていない)コードのようなものがその仕事をするはずです:
// Functions to swap the endian of 16 and 32 bit values
inline void SwapEndian(UINT16 &val)
{
val = (val<<8) | (val>>8);
}
inline void SwapEndian(UINT32 &val)
{
val = (val<<24) | ((val<<8) & 0x00ff0000) |
((val>>8) & 0x0000ff00) | (val>>24);
}
次に、構造体をロードしたら、各要素を交換するだけです。
SwapEndian(r.foo);
SwapEndian(r.bar);
SwapEndian(r.baz);
実際、エンディアンは、OSではなく、基盤となるハードウェアのプロパティです。
最善の解決策は、データを書き込むときに標準に変換することです。Googleは「ネットワークバイトオーダー」を意味し、これを行う方法を見つける必要があります。
編集:ここにリンクがあります: http://www.gnu.org/software/hello/manual/libc/Byte-Order.html
ファイルから構造体に直接読み込まないでください!パッキングは異なる場合があります。プラグマパックまたは同様のコンパイラ固有の構造をいじる必要があります。信頼性が低すぎます。コードが多数のアーキテクチャやシステムでコンパイルされていないため、多くのプログラマーはこれを回避しますが、それはそれが問題ないという意味ではありません!
良い代替アプローチは、ヘッダーをバッファに読み込み、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」ノードを中間コードに挿入しますが、これはかなり効率的です。アラインメントが問題である場合、完全な読み取りシフトまたはシーケンスが生成される可能性がありますが、それでもそれほど粗末ではありません。比較ブランチは、アドレスLSBをテストし、解析の高速バージョンまたは低速バージョンを使用できるかどうかを確認する場合に、最適化を可能にする可能性があります。しかし、これはすべての読み取りでのテストのペナルティを意味します。努力する価値がないかもしれません。
ああ、そうです、私たちはヘッダーなどを読んでいます。それがあまりにも多くのアプリケーションのボトルネックであるとは思いません。一部のコーデックが本当にタイトな内部ループを実行している場合も、一時バッファに読み込んでそこからデコードすることをお勧めします。同じ原則..大量のデータを処理するときに、ファイルから一度にバイトを読み取る人は誰もいません。ええと、実際、私はその種のコードを頻繁に見ました、そして「なぜあなたがそれをするのか」に対する通常の応答は、ファイルシステムがブロック読み取りを行い、バイトはとにかくメモリから来るということです、本当ですが、それらは深いコールスタックを通過しますこれは、数バイトを取得するための高オーバーヘッドです!
それでも、パーサーコードを1回記述し、無数の回数を使用します->エピックウィン。
ファイルから構造体を直接読み取る:それを実行しないでください!
struct
全体ではなく、各メンバーに個別に影響します。また、配列などには影響しません。たとえば、int
sのバイトを逆の順序で格納するだけです。
PS。とはいえ、奇妙なエンディアンを備えたマシンが存在する可能性があります。私が今言ったことは、ほとんどの使用済みマシン(x86、ARM、PowerPC、SPARC)に当てはまります。
次のように、スワッピングが必要なデータ型ごとにSwapBytesメソッドを実装するのが好きです。
inline u_int ByteSwap(u_int in)
{
u_int out;
char *indata = (char *)∈
char *outdata = (char *)&out;
outdata[0] = indata[3] ;
outdata[3] = indata[0] ;
outdata[1] = indata[2] ;
outdata[2] = indata[1] ;
return out;
}
inline u_short ByteSwap(u_short in)
{
u_short out;
char *indata = (char *)∈
char *outdata = (char *)&out;
outdata[0] = indata[1] ;
outdata[1] = indata[0] ;
return out;
}
次に、次のように、スワッピングが必要な構造に関数を追加します。
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UNIT16 baz;
void SwapBytes()
{
foo = ByteSwap(foo);
bar = ByteSwap(bar);
baz = ByteSwap(baz);
}
}
次に、次のように構造を読み取る(または書き込む)コードを変更できます。
fstream f;
f.open("file.bin", ios::in | ios::binary);
RECORD r;
f.read((char*)&detail, sizeof(RECORD));
r.SwapBytes();
cout << "fooword = " << r.fooword << endl;
さまざまなプラットフォームをサポートするには、各ByteSwapオーバーロードのプラットフォーム固有の実装が必要です。
複数のバイトの各メンバーのエンディアンを個別に修正する必要があります。文字列はバイトのシーケンスと見なすことができるため、変換する必要はありません(foowordおよびbarword)。
ただし、別の問題に注意する必要があります。それは、構造体のメンバーの一致です。基本的に、sizeof(RECORD)がUNIXコードとWindowsコードの両方で同じであるかどうかを確認する必要があります。コンパイラーは通常、必要なアラインメントを定義するためのプラグマを提供します(たとえば、#pragmapack)。
また、2つのコンパイラ間の配置の違いも考慮する必要があります。各コンパイラーは、アーキテクチャーに最適な構造体のメンバー間にパディングを挿入できます。だからあなたは本当に知る必要があります:
これが、ほとんどのプログラム(私が見た(プラットフォームに依存しない必要がある))が、標準のiostreamで簡単に読み取れるテキストストリームとしてデータをシリアル化する理由です。
このようなものが機能するはずです:
#include <algorithm>
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UINT16 baz;
}
void ReverseBytes( void *start, int size )
{
char *beg = start;
char *end = beg + size;
std::reverse( beg, end );
}
int main() {
fstream f;
f.open( "file.bin", ios::in | ios::binary );
// for each entry {
RECORD r;
f.read( (char *)&r, sizeof( RECORD ) );
ReverseBytes( r.foo, sizeof( UINT32 ) );
ReverseBytes( r.bar, sizeof( UINT32 ) );
ReverseBytes( r.baz, sizeof( UINT16 )
// }
return 0;
}