ビットフィールド
構造体:インデックス への移動
ビットフィールドは指定された数のビットで,関連する識別子を持つ場合も持たない場合もあります。ビットフィールドは,構造体(struct,union,class)をユーザーが定義したサイズの名前付き部分に細分化する方法を提供します。
ビットフィールドの宣言
ビットフィールド幅とオプションの識別子を次のように指定します。
型指定子 < ビットフィールド識別子 > : 幅;
C++ の型指定子には bool,char,unsigned char,short,unsigned short,long,unsigned long,int,unsigned int,__int64,unsigned __int64 があります。厳密な ANSI C の型指定子は int または unsigned int です。
式 width が指定され,定整数と評価されなければなりません。C++ では,ビットフィールドの幅はどのサイズで宣言してもかまいません。厳密な ANSI C では,ビットフィールド幅のサイズは宣言された型のサイズ以内で宣言します。ゼロ長のビットフィールドは,次の割り当てユニットまでスキップします。
ビットフィールド識別子を省略すると,幅で指定されたビット数は割り当てられますが,そのフィールドにはアクセスできません。これによって,未使用のビットがあるハードウェアレジスタ内のビットパターンの調整を行うといったことが可能になります。
ビットフィールドは,構造体,共用体,およびクラス内でのみ宣言することができます。これらは,ビットフィールド以外のメンバーに使用されるのと同じメンバー選択子(. と ->)を用いてアクセスされます。
ビットフィールド使用上の制限
ビットフィールドを使用する際には,以下の制限事項に注意してください。
- バイト内のビットおよびワード内のバイトの構成はマシンに依存するため,移植を目的としたコードを作成する際にはビットフィールドについて問題が生じることがある。
- ビットフィールドのアドレスは取得できない。このため,&mystruct.x という式は,x がビットフィールド識別子であれば不正になる。これは,mystruct.x が,バイトアドレスに位置している保証がないからである。
- ビットフィールドを使用すると,より多くの変数をより小さいデータ空間にパッキングできる。ただし,コンパイラは追加コードを生成してこれらの変数を操作することになり,コードは膨張して実行時間が長くなってしまう。
こうした制限事項があるために,一般的には低レベルのプログラミングを除いてビットフィールドを使うことは推奨されません。1 ビット変数,つまりフラグを設定する代替策としては,下記の例のように define を使うことが推奨されます。次に例を示します。
- define nothing 0x00
- define bitOne 0x01
- define bitTwo 0x02
- define bitThree 0x04
- define bitFour 0x08
- define bitFive 0x10
- define bitSix 0x20
- define bitSeven 0x40
- define bitEight 0x80
これらを使用して,次のようなコードを記述できます。
if (flags & bitOne) {...} // オンにされた bit One
flags |= bitTwo; // bit Two をオンにする
flags &= ~bitThree; // bit Three をオフにする
任意のサイズのビットフィールドに対して同様のスキームを作成できます。
ビットフィールドのパディング
C++ では,幅サイズがビットフィールドの型よりも大きい場合,コンパイラは要求された幅サイズからビットフィールドの型のサイズを引いた分に等しいパディングを挿入します。このため,
struct mystruct
{
int i : 40;
int j : 8;
};
という宣言では,'i' 用の 32 ビットストレージ,8 ビットパディング,および 'j' 用の 8 ビットストレージが作成されます。アクセスを最適化するために,コンパイラは 'i' をビットフィールドではなく通常の int 変数とみなします。
レイアウトとアラインメント
ビットフィールドは,符合の有無にかかわらず,同じ型の連続するビットフィールドのグループに分割されます。ビットフィールドの各フィールドは,グループメンバーの型の現在のアラインメントに揃えられます。このアラインメントは,アラインメント全体の設定(バイトアラインメントオプション -aN で設定)によって型 AND で決定されます。各グループ内では,コンパイラは領域内のビットフィールドをその型サイズと同じ大きさにパックします。ただし,どのビットフィールドもそうした 2 領域間の境界をまたがることは許可されません。構造体全体のサイズは,現在のアラインメントの指定に揃えられます。
ビットフィールドのレイアウト,パディング,アラインメントの例
次の C++ 宣言の my_struct には,int,long,および char という 3 種類の型の 6 つのビットフィールドが入っています。
struct my_struct
{
int one : 8;
unsigned int two : 16;
unsigned long three : 8;
long four : 16;
long five : 16;
char six : 4;
};
ビットフィールドの 'one' および 'two' は単独の 32 ビット領域にパックされます。
次に,コンパイラは必要に応じて,現在のアラインメントに基づいてパディングと three の型を挿入します。それは,その型が変数 two と three の宣言の間で変化するからです。たとえば,現在のアラインメントがバイトアラインメント(-a1)であればパディングは不要ですが,アラインメントが 4 バイト(-a4)であれば 8 ビットパディングが挿入されます。
次に,変数 three,four,および five はすべて型 long です。変数 three と four は単独の 32 ビット領域にパックされますが,five は 40 ビット領域(long 型に許可された 32 ビットより大きい)を作成するため同じ領域にはパックできません。five の新しい領域を開始するために,コンパイラは現在のアラインメントがバイトアラインメントならパディングを挿入しませんし,dword (4 バイト) アラインメントなら 8 ビットのパディングを挿入します。
変数 six では,型が再び変化します。char は常にバイトアラインメントなので,パディングは不要です。コンパイラは struct 全体のアラインメントを強制するために,バイトアラインメントの使用時には最後の領域を 4 ビットのパディングで,dword アラインメントの使用時には 12 ビットのパディングで終了します。
my_struct の合計サイズはバイトアラインメントでは 9 バイト,dword アラインメントでは 12 バイトです。
ビットフィールドの使用時に最適な結果を得るには,以下の処理を行います。
- 型別にビットフィールドをソートする
- ビットフィールドが領域境界をまたがらないように整理して,間違いなく領域内にパックする
- struct ができるだけ満たされるようにする
このほか,struct のバイトアラインメントを強制する方法としては,"#pragma option -a1" を発行することも推奨できます。struct の大きさを確認する際には,それに続けて "#pragma sizeof(mystruct)" を指定すると,サイズが返されます。
1 ビット長の符号付きフィールドの使い方
ビット長の符号付きタイプでは,0 または -1 の値しか持てません。1 ビット長の符号なしタイプでは,値は 0 または 1 となります。符号付きビットフィールドに「1」を割り当てた場合,値は「-1」(負数 1)として評価されます。
値の true と false を符号付きタイプの 1 ビット長ビットフィールドに格納しても,そのフィールドの保持できる値は '0' と '-1'(true と false には対応しない)しかないので,true に等しいかどうかをテストすることはできません。ただし,ゼロ以外かどうかをテストすることはできます。
すべてのタイプの符号なしバリエーションと,bool 型ではもちろん,true に等しいかどうかのテストは予想どおりに機能します。
このため,
struct mystruct
{
int flag : 1;
} M;
int testing()
{
M.flag = true;
if(M.flag == true)
printf("success");}
は機能しませんが,
struct mystruct
{
int flag : 1;
} M;
int testing()
{
M.flag = true;
if(M.flag)
printf("success");
}
は適切に機能します。
互換性についての注意
バージョンの違うコンパイラやほかのコンパイラとの間で互換性を確保するために,デフォルトアラインメントに変更を加えることができます。結果として,ビットフィールドのアラインメントが変更される場合があります。このため,ビットフィールドのアラインメントについて,バージョンの違う コンパイラ間で矛盾がないとは言えません。コード内のビットフィールドの下位互換性をチェックする際には,予期している構造体のサイズをチェックするアサートを追加します。
ビットフィールドのアラインメントと格納は,C および C++ 言語の仕様にしたがって実装定義されます。このため,コンパイラはビットフィールドのアラインメントと格納を別々に行うことができます。ビットフィールドのレイアウトについて完全に制御するのであれば,開発者独自のビットフィールドアクセスルーチンを記述して独自のビットフィールドを作成することが推奨されます。