ビットフィールド

MISRA-C ビットフィールドの規定 組込みエンジニアとしての活動

はじめに

ビットフィールドの動作について処理系によってかなり違うので示します。

ビット位置が意識されたデータに対してはビットフィールドは使用しない。

NG使用例

struct S {
 unsigned int bit1:1;
 unsigned int bit2:1;
};
ext ern struct S * p;
// 例えばpがIOポートを指しているなどビット
// 位置が意味をもつ、すなわち、bit1がデータの
// 最低位ビットを指すか、最高位ビットを指すか
// に意味がある場合
は、移植性がない
p-> bit1 = 1; // pが指しているデータのどのビット
// に設定されるかは処理系依存

OK使用例

(2)の適合例
struct S {
 unsigned int bit1:1;
 unsigned int bit2:1;
};
ext ern struct S * p;
// 例えばpは、単なるフラグの集合を指している
// など、pが指すデータ中、bit1がどのビットで
// あってもよい場合は、OK
p->bit1 = 1;

ビットフィールドは、次の動作がコンパイラによって異なる。

(1)符号指定のないint型のビットフィールドが符号付と扱われるかどうか。

  1. フィールド幅がint型より小さい場合、int型に拡張されます。
  2. フィールド幅がint型と等しい場合、unsigned intに拡張されます。
  3. 符号付きか符号無しかによって、同じビットパターンでも異なる値として解釈される可能性があります

対策

 このような処理系依存の動作を避けるため、ビットフィールドを使用する際は明示的に符号付き(signed int)または符号無し(unsigned int)を指定することが推奨されます

(2)単位内のビットフィールドの割り付け順序 

単位内のビットフィールドの割り付け順序は処理系定義であり、主に2つの方式があります

1.最下位ビットから最上位ビットへの割り当て

 Microsoft Visual C++では、ビットフィールドは整数内で最下位ビットから最上位ビットへと割り当てられます。例えば、以下のような構造体の場合

struct mybitfields {
 unsigned a : 4;
 unsigned b : 5;
 unsigned c : 7;
} test;

ビットは次のように配置されます:

00000001 11110010
cccccccb bbbbaaaa

2.最上位ビットから最下位ビットへの割り当て

一部の処理系では、ビットフィールドが上位から下位へ向かって配置されることもあります

注意点

  • ビットフィールドの配置順序は、エンディアンとは別の概念です。例えば、80×86プロセッサは整数値の下位バイトを上位バイトの前に格納しますが、これはビットフィールドの内部配置とは異なります。
  • ビットフィールドの配置順序によって、構造体全体のサイズが変わる可能性があります。適切な順序で宣言することで、メモリ使用量を最適化できる場合があります。
  • 移植性を考慮する場合、ビットフィールドの配置順序に依存しないコードを書くことが推奨されます以下に、その説明をします。
対策
  • ビットフィールドの個々のメンバに直接アクセスし、操作する
  • ビットフィールド全体をポインタ経由で操作することを避ける
  • ビットフィールドの順序や配置に依存した演算を行わない
適切なコード例

struct bf {
 unsigned int m1 : 8;
 unsigned int m2 : 8;
 unsigned int m3 : 8;
 unsigned int m4 : 8;
};

void function() {
  struct bf data;
  data.m1 = 0;
  data.m2 = 0;
  data.m3 = 0;
  data.m4 = 0;
  data.m1++; // 特定のフィールドを直接操作
}

(3)ビットフィールドを記憶域単位の境界

 ビットフィールドが記憶域単位(通常32ビット)の境界をまたぐことを許可するかどうかが処理系によって異なります

ビットフィールドを記憶域単位の境界

 ビットフィールドを記憶域単位の境界に合わせて配置することは、メモリ効率と処理速度の観点から重要です。以下にその詳細を説明します

 ビットフィールドを記憶域単位の境界に合わせて配置することは、メモリ効率と処理速度の観点から重要です。以下にその詳細を説明します

1.記憶域単位の概念

記憶域単位とは、ビットフィールドが配置される基本的なメモリブロックのことです。多くの処理系では、この単位は32ビット(4バイト)です

2.境界合わせの重要性
  • メモリ効率:適切に配置することで、無駄なパディングを減らし、構造体全体のサイズを最小化できます。
  • アクセス速度:境界に合わせることで、CPUがデータにアクセスする際の効率が向上します。
3.配置の制御方法

メンバの順序:ビットフィールドのメンバを適切な順序で宣言することで、効率的な配置が可能です。

サイズ0のビットフィールド:名前なしで:0と指定すると、次のビットフィールドを強制的に新しい記憶域単位に配置できます

4.注意点
  • 処理系依存:ビットフィールドの実装は処理系に大きく依存します。移植性を考慮する場合は注意が必要です。
  • エンディアン:ビッグエンディアンとリトルエンディアン環境で同じメモリレイアウトになるよう考慮が必要な場合があります
5.ベストプラクティス
  • 明示的な制御:サイズ0のビットフィールドを使用して、明示的にメモリレイアウトを制御します。
  • 慎重な設計:構造体のメンバ順序を慎重に設計し、メモリ効率を最適化します。
  • 検証:複数の処理系で動作を確認し、想定通りのレイアウトになっているか検証します

ビットフィールドを記憶域単位の境界に合わせて適切に配置することで、メモリ効率と処理速度の向上が期待できます。ただし、処理系依存の特性を十分に理解し、必要に応じてテストと検証を行うことが重要です

IOポートへのアクセスのように、ビットフィールドを使用する。

 例えばIOポートへのアクセスのように、ビット位置が意味をもつデータのアクセスに使用すると、jjy上記の(2)、(3)の点から移植性に問題がある。そのため、そのような場合にはビットフィールドは利用せず、&や|などのビット単位の演算を使用する。

まとめ

 いかがでしたか?IO入力でビットフィールドを使わないということですね。だけではないですが、ビットフィールドの処理系定義の動作に依存したプログラミングを作成している場合は、MISRA-Cの規定では、文書化はしなければいけないです。


コメント

タイトルとURLをコピーしました