C 言語 | 構造体宣言 | ビットフィールド
構造体のメンバーを細かなビット単位に分割して使用する方法を説明する。ビット単位に分けた複数の情報を 1 つの構造体にまとめることができる。
ビット単位の分割
ビットフィールドは、構造体のメンバーに複数の値を細かなビット単位で分けて格納したい場合に役立つ。
たとえば、電子音楽の規格である MIDI(Musical Instrument Digital Interface)がある。このプロトコルは 1980 年代初頭の 8 ビット CPU の時代に作られ、現在も電子楽器の分野で利用されている。当時のプロセッサは現在よりはるかに低速で、メモリも高価で容量が小さかった。情報を効率よく伝達し、少ないメモリで高速に処理することが、プログラムの簡潔さや構造の美しさより重視される場合もあった。その手段の 1 つが、1 バイトをビット単位のデータに分割して格納する方法である。
ビットフィールドを使うと、2 つの情報を 4 ビットずつに分け、1 バイトに圧縮して送信できる。実装に依存する数値型の実体をビット単位のフィールドに分割して利用するため、4 ビットや 6 ビットのメンバーを作成できる。ただし、ビットフィールドにはアドレスが存在しないため、ポインターで参照することはできない。
ビットフィールドを使用するには、構造体宣言のメンバー名の後ろにコロン(:)とフィールドのビット数を指定する。
struct Msg {
unsigned int type : 1;
unsigned int attr : 3;
unsigned int id : 4;
};
この Msg 構造体には、1 ビットの type、3 ビットの attr、4 ビットの id というメンバーがある。符号なし整数であることを明確にするため、一般にビットフィールドの宣言には unsigned int を使用する。
ただし、ビットフィールドを持つ構造体のインスタンスがどのようなメモリ構造になるかは、実装に大きく依存する。バイト境界をまたぐビットフィールドがメモリ上でどのように表現されるかは、C 言語では規定されていない。記憶単位の上位ビットから割り当てる場合もあれば、下位ビットから割り当てる場合もある。
コード 1
#include <stdio.h>
struct MidiMsg {
unsigned int status : 4;
unsigned int channel : 4;
unsigned int second : 8;
unsigned int third : 8;
};
int main() {
struct MidiMsg msg = { 9 , 0 , 0x3C , 40 };
printf(
"status = %d\nchannel = %d\n"
"second = %d\nthird = %d\n" ,
msg.status , msg.channel , msg.second , msg.third
);
return 0;
}
コード 1 では、ビットフィールドを持つ MidiMsg 構造体を宣言している。この構造体は MIDI ハードウェアへ渡すメッセージを表す。MIDI の知識は必要ない。MIDI メッセージの status と channel が 4 ビット単位に分割されている点に注目しよう。たとえば、32 ビットコンピューターでは次のような構造になると考えられる。
表 1 - 32 ビットコンピューターにおける MidiMsg 構造体の構成例
| unsigned int: 32 ビット |
|---|
| status: 4 ビット | channel: 4 ビット | second: 8 ビット | third: 8 ビット | 未使用領域: 8 ビット |
繰り返しになるが、ビットフィールドがどのように配置されるかは実装によって異なる。ただし、実行結果は期待した値になるはずである。ビット単位に正しく分割されていることを確認するには、status メンバーへ 0xF より大きな値を代入するとよい。status メンバーは 4 ビットなので、それを超える上位ビットは切り捨てられる。