7

基本的な質問ですが、この構造体は13バイトのスペースを占めると予想していました(charの場合は1、unsigned intの場合は12)。代わりに、sizeof(ESPR_REL_HEADER)16バイトを与えます。

typedef struct {
  unsigned char version;
  unsigned int  root_node_num;
  unsigned int  node_size;
  unsigned int  node_count;
} ESPR_REL_HEADER;

私がやろうとしているのは、この構造体をいくつかの値で初期化し、そこに含まれるデータ(生のバイト)をファイルの先頭に書き込むことです。これにより、後でこのファイルを開いたときに、この構造体を再構築してメタデータを取得できます。ファイルの残りの部分に含まれるものに関するデータ。

構造体を初期化して、次のようにファイルに書き込んでいます。

int esprime_write_btree_header(FILE * fp, unsigned int node_size) {
  ESPR_REL_HEADER header = {
    .version       = 1,
    .root_node_num = 0,
    .node_size     = node_size,
    .node_count    = 1
  };

  return fwrite(&header, sizeof(ESPR_REL_HEADER), 1, fp);
}

node_size私が実験している間、現在4はどこにありますか。

構造体を書き込んだ後、ファイルには次のデータが含まれています。

-bash$  hexdump test.dat
0000000 01 bf f9 8b 00 00 00 00 04 00 00 00 01 00 00 00
0000010

私はそれが実際に含まれていることを期待しています:

-bash$  hexdump test.dat
0000000 01 00 00 00 00 04 00 00 00 01 00 00 00
0000010

失礼します。私は学ぼうとしています:)構造体のデータコンポーネントだけをファイルに効率的に書き込むにはどうすればよいですか?

4

8 に答える 8

6

マイクロプロセッサは、任意のアドレスからデータをフェッチするようには設計されていません。4 バイトの s などのオブジェクトは、4intで割り切れるアドレスにのみ格納する必要があります。この要件はアライメントと呼ばれます。

C では、構造体メンバー間にパディング バイトを挿入して整列させる自由がコンパイラに与えられます。パディングの量は、異なるプラットフォーム間の変数の 1 つに過ぎず、もう 1 つの主要な変数はendiannessです。これが、プログラムを複数のマシンで実行したい場合に、単純に構造をディスクに「ダンプ」するべきではない理由です。

ベスト プラクティスは、各メンバーを明示的に記述し、htonlバイナリ出力の前にエンディアンをビッグ エンディアンに固定するために使用することです。読み戻すときはmemcpy、未加工のバイトを移動するために使用し、使用しないでください

char *buffer_ptr;
...
++ buffer_ptr;
struct.member = * (int *) buffer_ptr; /* potential alignment error */

しかし、代わりに

memcpy( buffer_ptr, (char *) & struct.member, sizeof struct.member );
struct.member = ntohl( struct.member ); /* if member is 4 bytes */
于 2012-04-14T11:22:20.553 に答える
3

これは構造パディングのためです。http://en.wikipedia.org/wiki/Sizeof#Implementationを参照してください。

于 2012-04-14T11:18:51.690 に答える
1

私は TPL と呼ばれる Troy D. Hanson によって書かれた素晴らしいオープン ソース コードを使用しています: http://tpl.sourceforge.net/。TPL を使用すると、外部依存関係がなくなります。tpl.c と tpl.h を独自のプログラムにインクルードし、TPL API を使用するのと同じくらい簡単です。

ここにガイドがあります: http://tpl.sourceforge.net/userguide.html

于 2012-04-14T11:37:20.630 に答える
1

構造体はアライメント規則に従います。つまり、その中のいくつかの項目がパディングされます。それを見ると、最初のunsigned charフィールドが 4 バイトにパディングされているように見えます。

ここでの落とし穴の 1 つは、ルールがシステムごとに異なる可能性があることです。そのため、fwriteあるプラットフォーム上の 1 つのコンパイラでコンパイルされたプログラムで構造体全体を使用して記述し、fread別のコンパイラを使用してそれを読み取ろうとすると、次のようになる可能性があります。 2 番目のプログラムは、構造体レイアウトの概念に合わせてデータが配置されていると想定するため、ガベージを取得します。

通常、次のいずれかを行う必要があります。

  1. 保存されたデータ ファイルが、特定の特性を共有するプログラムのビルドに対してのみ有効であることを決定する (使用したコンパイラの文書化された動作に応じて)、または

  2. 構造全体を 1 つとして記述するのではなく、各要素が個別に記述され、そのサイズが明示的に制御される、より正式なデータ形式を実装します。

(関連する問題は、バイト順が異なる可能性があることです。オプション 2 でデータ形式のバイト順を明示的に指定することを除いて、同じ選択が一般的に適用されます。)

于 2012-04-14T11:23:59.940 に答える
1

これは、メモリ アラインメントと呼ばれるものが原因です。最初の char は、4 バイトのメモリを使用するように拡張されます。実際、 のような大きな型intは 4 バイトのブロックの先頭からしか「開始」できないため、コンパイラはこのポイントに到達するまでバイトをパディングします。

2文字から始まるビットマップヘッダーでも同じ問題がありました。私char bm[2]は構造体の中で a を使用し、#$%^ ヘッダーの 3 番目と 4 番目のバイトがどこに行くのか 2 日間疑問に思いました...

これを防ぎたい場合は使用できます__attribute__((packed))が、注意してください。プログラムを便利に実行するに、メモリアラインメントが必要です。

于 2012-04-14T11:25:50.627 に答える
1

これをしないように頑張ってください!サイズの不一致は、速度によって変数へのアクセスを最適化するためにコンパイラ/リンカーによって使用されるパディングとアライメントによって引き起こされます。言語と OS のパディングとアラインメントの規則。さらに、異なるハードウェアで int を書き込んで読み取ると、エンディアンが原因で問題が発生する可能性があります。

メタデータを、誤解されない構造でバイト単位で記述します。NULL で終わる ASCII 文字列は問題ありません。

于 2012-04-14T11:26:51.273 に答える
1

を使用して構造体をそのまま書き込むと、パディングfwriteによって挿入された構造体内の「デッド バイト」を含め、メモリ内にそのまま書き込まれます。さらに、マルチバイト データはシステムのエンディアンで書き込まれます。

これが発生したくない場合は、構造体からデータをシリアル化する関数を記述します。パディングされていない領域のみを書き込むことができ、マルチバイト データを予測可能な順序 (ネットワーク バイト順など) で書き込むこともできます。

于 2012-04-14T11:23:27.043 に答える
0

データを特定の形式で書き込みたい場合は、次の配列を使用しunsigned charます...

unsigned char outputdata[13];
outputdata[0] = 1;
outputdata[1] = 0;
/* ... of course, use data from struct ... */
outputdata[12] = 0;
fwrite(outputdata, sizeof outputdata, 1, fp);
于 2012-04-14T11:24:02.120 に答える