68

ビットフィールドを使用する移植可能なコードは、リトルエンディアン プラットフォームとビッグ エンディアン プラットフォームを区別しているようです。このようなコードの例については、Linux カーネルの struct iphdrの宣言を参照してください。ビットエンディアンが問題になる理由がまったくわかりません。

私が理解している限り、ビットフィールドは純粋にコンパイラ構造であり、ビットレベルの操作を容易にするために使用されます。

たとえば、次のビットフィールドを考えてみます。

struct ParsedInt {
    unsigned int f1:1;
    unsigned int f2:3;
    unsigned int f3:4;
};
uint8_t i;
struct ParsedInt *d = &i;
ここでの書き込みd->f2は、コンパクトで読みやすい言い方です(i>>1) & (1<<4 - 1)

ただし、ビット操作は明確に定義されており、アーキテクチャに関係なく機能します。では、なぜビットフィールドは移植可能ではないのでしょうか?

4

7 に答える 7

92

C標準では、コンパイラはビットフィールドを任意のランダムな方法で自由に格納できます。ビットがどこに割り当てられているかを推測することはできません。C標準で指定されていないビットフィールド関連のものをいくつか示します。

不特定の動作

  • ビットフィールドを保持するために割り当てられたアドレス指定可能なストレージユニットの配置(6.7.2.1)。

実装定義の動作

  • ビットフィールドがストレージユニットの境界にまたがることができるかどうか(6.7.2.1)。
  • ユニット内のビットフィールドの割り当ての順序(6.7.2.1)。

ビッグ/リトルエンディアンももちろん実装定義です。これは、構造体が次の方法で割り当てられる可能性があることを意味します(16ビットintを想定)。

PADDING : 8
f1 : 1
f2 : 3
f3 : 4

or

PADDING : 8
f3 : 4
f2 : 3
f1 : 1

or

f1 : 1
f2 : 3
f3 : 4
PADDING : 8

or

f3 : 4
f2 : 3
f1 : 1
PADDING : 8

どちらが当てはまりますか?推測するか、コンパイラの詳細なバックエンドドキュメントを読んでください。これに、ビッグエンディアンまたはリトルエンディアンの32ビット整数の複雑さを追加します。次に、コンパイラがビットフィールド内の任意の数のパディングバイトを追加できるという事実を追加します。これは、構造体として扱われるためです(構造体の最初にパディングを追加することはできませんが、それ以外の場所には追加できません)。

そして、ビットフィールドタイプ=実装定義の動作としてプレーンな「int」を使用した場合、または(符号なし)int =実装定義の動作以外のタイプを使用した場合に、何が起こるかについても触れていません。

したがって、質問に答えるために、ポータブルビットフィールドコードのようなものはありません。C標準はビットフィールドの実装方法について非常にあいまいだからです。ビットフィールドが信頼できる唯一のことは、ブール値のチャンクであり、プログラマーはメモリ内のビットの位置を気にしません。

唯一の移植可能な解決策は、ビットフィールドの代わりにビット単位の演算子を使用することです。生成されるマシンコードはまったく同じですが、決定論的です。ビット単位の演算子は、どのシステムのどのCコンパイラでも100%移植可能です。

于 2011-05-18T11:51:27.410 に答える
19

私が理解している限り、ビットフィールドは純粋にコンパイラの構造です

そして、それは問題の一部です。ビットフィールドの使用がコンパイラが「所有する」ものに制限されている場合、コンパイラがビットをパックしたり順序付けたりする方法は、誰にとってもほとんど問題になりません。

ただし、ビットフィールドはおそらく、コンパイラのドメインの外部にある構造 (ハードウェアレジスタ、通信用の「ワイヤ」プロトコル、またはファイル形式レイアウト) をモデル化するために、はるかに頻繁に使用されます。これらには、ビットをどのようにレイアウトする必要があるかという厳密な要件があり、ビットフィールドを使用してそれらをモデル化するということは、実装定義に依存する必要があることを意味し、さらに悪いことに、コンパイラがビットフィールドをレイアウトする方法の未指定の動作に依存する必要があります.

要するに、ビットフィールドは、最も一般的に使用されると思われる状況で役立つように十分に指定されていません。

于 2011-05-18T14:37:46.227 に答える
10

ISO/IEC 9899: 6.7.2.1/10

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

システムのエンディアンやビット数に関係なく、移植可能なコードを記述しようとするときは、ビット フィールドの順序やアラインメントを仮定する代わりに、ビット シフト操作を使用する方が安全です。

EXP11-Cも参照してください。1 つの型を期待する演算子を互換性のない型のデータに適用しないでください

于 2011-05-18T12:08:24.220 に答える
8

ビット フィールド アクセスは、基になる型の操作に関して実装されます。例では、unsigned int. したがって、次のようなものがある場合:

struct x {
    unsigned int a : 4;
    unsigned int b : 8;
    unsigned int c : 4;
};

fieldbにアクセスすると、コンパイラは全体unsigned intにアクセスしてから、適切なビット範囲をシフトおよびマスクします。(そうである必要はありません、そうであるふりをすることができます。)

ビッグ エンディアンでは、レイアウトは次のようになります (最上位ビットが最初)。

AAAABBBB BBBBCCCC

リトルエンディアンでは、レイアウトは次のようになります。

BBBBAAAA CCCCBBBB

リトル エンディアンからビッグ エンディアンのレイアウトにアクセスしたい場合、またはその逆の場合は、追加の作業を行う必要があります。この移植性の向上にはパフォーマンス上のペナルティがあり、構造体レイアウトはすでに移植性がないため、言語の実装者はより高速なバージョンを使用しました。

これは多くの仮定を行います。sizeof(struct x) == 4また、ほとんどのプラットフォームで注意してください。

于 2011-05-18T10:56:59.590 に答える
1

ビットフィールドは、マシンのエンディアンに応じて異なる順序で格納されます。これは、問題にならない場合もあれば、問題になる場合もあります。たとえば、ParsedInt構造体がネットワーク経由で送信されるパケットのフラグを表しているとすると、リトルエンディアンマシンとビッグエンディアンマシンは、送信されたバイトとは異なる順序でこれらのフラグを読み取ります。これは明らかに問題です。

于 2011-05-18T11:00:46.403 に答える