C / C ++:ビットフィールドの順序と配置を強制する

2009年09月29日に質問されました。  ·  閲覧回数 75.5k回  ·  ソース

dewald picture
2009年09月29日

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

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それに続きました。 さまざまなコンパイラで同等の構造体パッキングオプションを見つけた場合、これはクロスプラットフォームになりますか?

回答

Stephen Canon picture
2009年09月29日
103

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

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

Joshua picture
2009年09月29日
45

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

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

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

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

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

Michael Burr picture
2009年09月29日
35

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

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

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

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

pierrotlefou picture
2009年09月29日
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
Bob Murphy picture
2009年09月29日
6

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

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

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

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

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

Duncan Roe picture
2019年09月27日
1

非常に有益なコメントを始めてくれてありがとう@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で機能させることを諦めています。

99999999 picture
2009年09月29日
-9

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