WisdomSoft - for your serial experiences.

7.5 ビットフィールド

構造体の複数のメンバを細かいビット単位に分割して利用する方法を解説します。ビット単位に切り分けられた複数の情報を 1 つの構造体にパッケージ化できます。

7.5.1 ビット単位の分割

ビットフィールドとは構造体のメンバに関する機能で、複数の値を細かいビット単位に切り分けて使用する場合に便利です。

例えば、電子音楽規格の MIDI (Musical Instrumunt Digital Interface) です。このプロトコルは 1980 年代初頭の 8bit CPU 時代の産物で、今も変わらず電子楽器の分野で利用されています。当時のプロセッサは現代よりもはるかに低速で、メモリも今とは比べ物にならないほど容量が少なく高価でした。当時は、いかに情報を効率的に伝達し、少ないメモリ領域でより高速に処理させるかという問題が、プログラムの簡単さや構造の美しさよりも優先されました。その手段として、バイトの中にビット単位のデータを分割して格納する方法が考えられます。

ビットフィールドは、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(
		"ステータス = %d\nチャンネル = %d\n"
		"ノート = %d\nベロシティ = %d\n" ,
		msg.status , msg.channel , msg.second , msg.third
	);

	return 0;
}
実行結果
コード1 実行結果

コード1では、ビットフィールドを持つ MidiMsg 構造体を宣言しています。この構造体は MIDI ハードウェアに送出するメッセージを表します。MIDI について知識は必要ありません。MIDI メッセージの status と channel 情報が 4 ビット単位で分割されているということに注目してください。例えば、この構造体は 32 ビットコンピュータでは次のような構成となることが予想されます。

表1 32 ビットコンピュータにおける MidiMsg 構造体の構造
unsigned int 32 bit
status 4 bit channel 4 bit second 8 bit third 8 bit 使われない領域 8 bit

繰り返しになりますが、ビットフィールドがどのようにインスタンス化されるかは実装に依存します。しかし、実行結果は予想通りだと思います。ビットフィールドがそのビット単位に正しく分解されているかを確認したいのであれば、status メンバに 0xF 以上の値を代入してみるとわかります。status メンバは 4 ビットなので、それ以上の値を代入されれば上位ビットが切り捨てられます。