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


87

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

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


17
バッファはビットではなくバイトのセットであるため、「バッファの最初の3ビット」は正確な概念ではありません。最初のバイトの最下位3ビットを最初の3ビットと見なしますか、それとも最上位3ビットと見なしますか?
caf

2
ネットワーク上を通過するとき、「バッファの最初の3ビット」は非常に明確に定義されていることがわかります。
ジョシュア

2
@Joshua IIRC、イーサネットは各バイトの最下位ビットを最初に送信します(これがブロードキャストビットが存在する理由です)。
TC。

「ポータブル」と「クロスプラットフォーム」とはどういう意味ですか?実行可能ファイルは、ターゲットOSに関係なく、順序に正しくアクセスします-または-コードはツールチェーンに関係なくコンパイルされますか?
ガレットクラボーン

回答:


103

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

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


たとえば、GCCは、ビットフィールドが実装ではなくABIに従って配置されていることに特に注意しています。したがって、単一のコンパイラーにとどまるだけでは、順序付けを保証するのに十分ではありません。アーキテクチャもチェックする必要があります。本当に、移植性にとっては少し悪夢です。
underscore_d

10
C標準がビットフィールドの順序を保証しなかったのはなぜですか?
アーロンキャンベル

7
バイト内のビットの「順序」を一貫して移植可能に定義することは困難であり、バイト境界を越える可能性のあるビットの順序ははるかに困難です。あなたが決めた定義は、かなりの量の既存の慣行と一致しません。
スティーブンキャノン

2
implementaiton-definedは、プラットフォーム固有の最適化を可能にします。一部のプラットフォームでは、ビットフィールド間のパディングによりアクセスを改善できます。32ビット整数の4つの7ビットフィールドを想像してください。8ビットごとに整列することは、バイト読み取りがあるプラットフォームにとって大幅な改善です。
peterchen 2016


45

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

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

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

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

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


K&Rは、事前に標準化されており、おそらく多くの分野で取って代わられていることを考えると、本当に有用なリファレンスと見なされていますか?
underscore_d 2016年

1
私のK&RはポストANSIです。
ジョシュア

1
今では恥ずかしいことです。ANSI後の改訂版がリリースされていることに気づいていませんでした。私の悪い!
underscore_d 2016年

35

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

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

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

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


2
ビットフィールドの順序は、コンパイル時に決定できます。
グレッグA.ウッズ

9
また、ビットフィールドは、プログラムの外部(つまり、ディスク上、レジスタ内、または他のプログラムによってアクセスされるメモリ内など)に外部表現を持たないビットフラグを処理する場合に非常に適しています。
グレッグA.ウッズ

1
@ GregA.Woods:これが本当の場合は、その方法を説明する回答を提供してください。...私は何かを見つけることができませんでしたが、あなたのコメントはそれをグーグルとき
mozzbozz

1
@ GregA.Woods:申し訳ありませんが、私が参照したコメントに書き込む必要がありました。つまり、「ビットフィールドの順序はコンパイル時に決定できる」ということです。私はそれとそれを行う方法について何もできません。
mozzbozz 2015年

2
ご覧持っ@mozzbozz planix.com/~woods/projects/wsg2000.cをと定義しての使用を検索_BIT_FIELDS_LTOHし、_BIT_FIELDS_HTOL
グレッグA.ウッズ

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

6
aとbの出力は、エンディアンがまだビット順序とバイト順序について話していることを示しています。
Windowsのプログラマ

ビット順序とバイト順序の問題がある素晴らしい例
Jonathan

1
実際にコードをコンパイルして実行しましたか?「a」と「b」の値は私には論理的ではないようです。基本的に、コンパイラはエンディアンのためにバイト内のニブルを交換すると言っています。「d」の場合、エンディアンはchar配列内のバイト順序に影響を与えないはずです(charが1バイト長であると仮定)。コンパイラーがそれを行った場合、ポインターを使用して配列を反復処理することはできません。一方、2つの16ビット整数の配列を使用した場合。例:uint16 data [] = {0x1234,0x5678}; その場合、リトルエンディアンシステムではdは間違いなく0x7856になります。
クラウス2017

6

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

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

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

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

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


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で機能させることを諦めています。


-9

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


5
私はそれはそれはモデルに作成されたハードウェアレジスタを表現するための非常にきれいな方法を、提供するのでビットフィールドを使用することが愚かであることの状態に間違っていると思う、Cで
trondd

13
@trondd:いいえ、メモリを節約するために作成されました。ビットフィールドは、メモリマップドハードウェアレジスタ、ネットワークプロトコル、ファイル形式などの外部データ構造にマップすることを目的としていません。それらが外部のデータ構造にマッピングされることを意図していた場合、パッキングの順序は標準化されていたでしょう。
Ben Voigt 2013年

2
ビットを使用すると、メモリを節約できます。ビットフィールドを使用すると、読みやすさが向上します。使用するメモリが少ないほど高速です。ビットを使用すると、より複雑なアトミック操作が可能になります。実世界のアウトアプリケーションでは、パフォーマンスと複雑なアトミック操作が必要です。この答えは私たちにはうまくいきません。
johnnycrash 2015

@BenVoigtはおそらく本当ですが、プログラマーがコンパイラー/ ABIの順序が必要なものと一致することを確認し、それに応じて迅速な移植性を犠牲にする場合、彼らは確かにその役割を果たすことができます。9 *に関しては、「現実世界のコーダー」のどの権威ある集団がビットフィールドのすべての使用を「非専門的/怠惰/愚か」と見なし、どこでこれを述べましたか?
underscore_d

2
使用するメモリが少ないほど、常に高速になるとは限りません。多くの場合、より多くのメモリを使用して読み取り後の操作を減らす方が効率的であり、プロセッサ/プロセッサモードはそれをさらに真実にすることができます。
デイブニュートン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.