91

構造体内のビットフィールドの順序はプラットフォーム固有であることを読みました。異なるコンパイラ固有のパッキングオプションを使用する場合はどうなりますか?これにより、データが書き込まれるときに正しい順序で保存されることが保証されますか?例えば:

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

GCCコンパイラを搭載したIntelプロセッサでは、フィールドは示されているようにメモリに配置されていました。Message.versionバッファの最初の3ビットであり、その後にMessage.type続きます。さまざまなコンパイラに相当する構造体パッキングオプションが見つかった場合、これはクロスプラットフォームになりますか?

4

7 に答える 7

110

いいえ、完全に移植することはできません。構造体のパッキングオプションは拡張機能であり、それ自体は完全に移植可能ではありません。それに加えて、C99§6.7.2.1のパラグラフ10は、「ユニット内のビットフィールドの割り当ての順序(高次から低次または低次から高次)は実装によって定義されます」と述べています。

たとえば、単一のコンパイラでも、ターゲットプラットフォームのエンディアンに応じて、ビットフィールドのレイアウトが異なる場合があります。

于 2009-09-29T01:31:06.957 に答える
47

ビットフィールドはコンパイラごとに大きく異なります。申し訳ありません。

GCCでは、ビッグエンディアンマシンはビットビッグエンドを最初にレイアウトし、リトルエンディアンマシンはビットをリトルエンドを最初にレイアウトします。

K&Rによると、「構造体の隣接する[ビット]フィールドメンバーは、実装に依存する方向で実装に依存するストレージユニットにパックされます。別のフィールドに続くフィールドが収まらない場合は、ユニット間で分割されるか、ユニットがパディング。幅0の名前のないフィールドは、このパディングを強制します...」

したがって、マシンに依存しないバイナリレイアウトが必要な場合は、自分で行う必要があります。

この最後のステートメントは、パディングによる非ビットフィールドにも適用されます。ただし、GCCですでに検出されているように、すべてのコンパイラには構造体のバイトパッキングを強制する方法があるようです。

于 2009-09-29T01:28:02.623 に答える
36

ビットフィールドは避ける必要があります。同じプラットフォームであっても、コンパイラ間での移植性はあまり高くありません。C99標準6.7.2.1/10-「構造と共用体の指定子」から(C90標準にも同様の表現があります):

実装は、ビットフィールドを保持するのに十分な大きさのアドレス可能なストレージユニットを割り当てることができます。十分なスペースが残っている場合、構造内の別のビットフィールドの直後に続くビットフィールドは、同じユニットの隣接するビットにパックされます。十分なスペースが残っていない場合、収まらないビットフィールドが次のユニットに配置されるか、隣接するユニットとオーバーラップするかは、実装によって定義されます。ユニット内のビットフィールドの割り当て順序(高次から低次または低次から高次)は、実装によって定義されます。アドレス指定可能なストレージユニットの配置は指定されていません。

ビットフィールドがint境界に「またがる」かどうかを保証することはできません。また、ビットフィールドがintのローエンドで始まるか、intのハイエンドで始まるかを指定することもできません(これは、プロセッサがビッグエンディアンまたはリトルエンディアン)。

ビットマスクを優先します。インライン(またはマクロ)を使用して、ビットを設定、クリア、およびテストします。

于 2009-09-29T05:11:53.207 に答える
11

エンディアンは、ビットオーダーではなくバイトオーダーについて話します。現在、ビットオーダーが修正されていることは99%確実です。ただし、ビットフィールドを使用する場合は、エンディアンを考慮に入れる必要があります。以下の例を参照してください。

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a
于 2009-09-29T01:31:33.650 に答える
6

ほとんどの場合、おそらく、しかしそれに農場を賭けないでください。あなたが間違っていると、あなたは大きく失うでしょう。

本当に同一のバイナリ情報が必要な場合は、ビットマスクを使用してビットフィールドを作成する必要があります。たとえば、メッセージにunsigned short(16ビット)を使用し、versionMask=0xE000などを作成して最上位の3ビットを表します。

構造体内の配置にも同様の問題があります。たとえば、Sparc、PowerPC、および680x0 CPUはすべてビッグエンディアンであり、SparcおよびPowerPCコンパイラの一般的なデフォルトは、構造体メンバーを4バイト境界に揃えることです。ただし、680x0に使用した1つのコンパイラは、2バイト境界でのみ整列され、整列を変更するオプションはありませんでした。

したがって、一部の構造体では、SparcとPowerPCのサイズは同じですが、680x0では小さく、一部のメンバーは構造体内の異なるメモリオフセットにあります。

これは、私が取り組んだ1つのプロジェクトの問題でした。これは、Sparcで実行されているサーバープロセスがクライアントにクエリを実行し、それがビッグエンディアンであることがわかり、ネットワーク上でバイナリ構造体を噴出してクライアントが対処できると想定するためです。そして、それはPowerPCクライアントではうまく機能し、680x0クライアントでは大クラッシュしました。私はコードを書きませんでした、そして問題を見つけるのにかなりの時間がかかりました。しかし、一度修正すれば簡単に修正できました。

于 2009-09-29T04:53:13.477 に答える
2

非常に有用なコメントを始めてくれてありがとう@BenVoigt

いいえ、メモリを節約するために作成されました。

Linuxソース、外部構造と照合するためにビットフィールドを使用します。/usr/include/linux/ip.hには、IPデータグラムの最初のバイトにこのコードがあります。

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

しかし、あなたのコメントに照らして、私はこれをマルチバイトビットフィールドfrag_offで機能させることを諦めています。

于 2019-09-27T03:27:32.010 に答える
-10

もちろん、最良の答えは、ビットフィールドをストリームとして読み取り/書き込みするクラスを使用することです。Cビットフィールド構造の使用は保証されていません。言うまでもなく、これを実際のコーディングで使用することは、専門家ではない/怠惰な/愚かなことと見なされます。

于 2009-09-29T05:17:04.177 に答える