2

私はネットワーク層プロトコルをコーディングしています.Cで定義されたパックされた構造のサイズを見つける必要があります.コンパイラは余分なパディングバイトを追加する可能性があるためsizeof、私の場合は機能が役に立たなくなります. Google を調べたところ___attribute(packed)___、コンパイラが余分なパディング バイトを追加するのを防ぐために、このようなものを使用できることがわかりました。しかし、これは移植可能なアプローチではないと思います。私のコードは、Windows と Linux 環境の両方をサポートする必要があります。

現在、コードで定義されているすべての構造体のパック サイズをマップするマクロを定義しました。以下のコードを検討してください。

typedef struct {
...
} a_t;

typedef struct {
...
} b_t;

#define SIZE_a_t 8;
#define SIZE_b_t 10;

#define SIZEOF(XX) SIZE_##XX;

次に、メイン関数で、上記のマクロ定義を以下のように使用できます:-

int size = SIZEOF(a_t);

このアプローチは機能しますが、最善のアプローチではない可能性があると思います。Cでこの問題を効率的に解決する方法に関する提案やアイデアはありますか?

以下の C 構造を考えてみましょう:-

typedef struct {
   uint8_t  a;
   uint16_t b;
} e_t;

Linux では、sizeof関数は 3 バイトではなく 4 バイトを返します。これを防ぐために、私は現在これを行っています:-

typedef struct {
   uint8_t  a;
   uint16_t b;
} e_t;

#define SIZE_e_t 3
#define SIZEOF(XX) SIZE_##e_t

これで、関数を呼び出すSIZEOF(e_t)と、4 ではなく 3 が返されるはずです。

4

4 に答える 4

4

sizeof 、構造体またはその他の C データ型のサイズを見つける移植可能な方法です。

あなたが直面している問題は、構造体が必要なサイズとレイアウトを持つようにする方法です。

#pragma packまたは__attribute__((packed))あなたのために仕事をするかもしれません。100% 移植性があるわけではありません (C 標準でのパッキングについては言及されていません) が、現在の目的には十分移植性があるかもしれませんが、将来、コードを他のプラットフォームに移植する必要があるかどうかを検討してください。また、安全でない可能性もあります。この質問この回答を参照してください。

唯一の 100% 移植可能なアプローチは、配列を使用してunsigned char、どのフィールドがどのバイト範囲を占めているかを追跡することです。もちろん、これははるかに面倒です。

于 2012-05-26T23:47:50.587 に答える
2

マクロは、構造体が意図したとおりにレイアウトされている場合に、構造体が持つべきと思われるサイズを示します。

それが に等しくない場合sizeof(a_t)は、パックされていると考えるコードを書いても、いずれにせよ機能しません。それらが等しいと仮定すると、sizeof(a_t)すべての目的に使用することもできます。それらが等しくない場合は、何らかのチェックのためにのみ使用する必要がありますSIZEOF(a_t) == sizeof(a_t)。これは失敗し、機能しないコードのコンパイルを防ぎます。

したがって、ヘッダー ファイルsizeof(a_t) == 8SIZEOF.

SIZEOFが実際には のように動作しないという事実は別として、それはすべてsizeofです。たとえばtypedef a_t foo; sizeof(foo);、明らかに では機能しない を考えてみましょうSIZEOF

于 2012-05-26T23:16:08.773 に答える
1

サイズを手動で指定する方が、sizeof を使用するよりも移植性が高いとは思いません。

サイズが変更されると、const で指定されたサイズが正しくなくなります。

パックされた属性は移植可能です。Visual Studio では#pragma packです。

于 2012-05-26T23:10:36.783 に答える
1

構造体にオーバーレイしてデータを読み書きしようとしないことをお勧めします。代わりに、概念的には printf/scanf に似ていますが、バイナリ データ形式を指定する形式指定子を使用する一連のルーチンを作成することをお勧めします。パーセント記号ベースのタグを使用するのではなく、単純にデータ形式のバイナリ エンコーディングを使用することをお勧めします。

シリアライゼーション/デシリアライゼーション ルーチン自体のサイズ、それらを使用するために必要なコードのサイズ、およびさまざまなデシリアライゼーション フォーマットを処理する能力の間のトレードオフを含む、いくつかの方法があります。最も単純な (そして最も簡単に移植できる) アプローチは、書式文字列を使用する代わりに、二重間接ポインターを取得してアイテムを個別に処理し、そこから何らかのデータ型を読み取り、適切にインクリメントするルーチンを持つことです。したがって:

uint32_t read_uint32_bigendian(uint8_t const ** src)
{
  uint8_t *p;
  uint32_t tmp;

  p = *src;
  tmp = (*p++) << 24;
  tmp |= (*p++) << 16;
  tmp |= (*p++) << 8;
  tmp |= (*p++);
  *src = p;
}

...
  文字バフ[256];
...
  uint8_t *buffptr = バフ;
  first_word = read_uint32_bigendian(&buffptr);
  next_word = read_uint32_bigendian(&buffptr);

このアプローチは単純ですが、コードのパッキングとアンパッキングに多くの冗長性があるという欠点があります。フォーマット文字列を追加すると、次のように簡略化できます。

#define BIGEND_INT32 "\x43" // または、適切なトークンが何であれ
  uint8_t *buffptr = バフ;
  read_data(&buffptr, BIGEND_INT32 BIGEND_INT32, &first_word, &second_word);

このアプローチでは、1 回の関数呼び出しで任意の数のデータ項目を読み取ることができ、buffptrデータ項目ごとに 1 回ではなく、1 回だけ渡します。一部のシステムでは、まだ少し遅い場合があります。別の方法として、ソースから受信するデータの種類を示す文字列を渡し、データの送信先を示す文字列または構造体も渡す方法があります。これにより、ソースの二重間接ポインタ、ソースでのデータの形式を示す文字列ポインタ、データのアンパック方法を示す構造体へのポインタ、およびターゲットデータを保持する構造体へのポインタ。

于 2012-05-27T17:40:22.223 に答える