新入社員の方が書いているソースにビット演算によるフラグ管理が登場したので、覚えることにした。
ビット演算なので、2進数とビット演算子を使用してフラグを管理する方法を考える。ここでのビット演算を使用したフラグの管理とは、2進数の各桁を1つのフラグとして見ることで、1つの変数で複数の状態を管理することを目的としている。
基礎知識
int型は4バイト(32ビット)までの符号付き整数をサポートしているので-2147483648 ~ 2147483647の範囲の数を表現できる。int型の符号なし版であるuint型(unsigned int)では0 ~ 4294967295の範囲の数を表現できる。
どうしてこのような範囲の数が表現できるかという点については、型の容量に着目すると分かりやすい。
int型は4バイトである。1バイトが8ビットなので4バイトは32ビットである。つまり、int型は32桁の2進数で表せる範囲の数をサポートする。32桁の2進数で表現できる数の範囲は0 ~ 4294967295である。
int型はマイナスの値も範囲に含めてサポートするので、マイナス符号(-)を表現する為に1ビット使用する。そのため、-2147483648 ~ 2147483647の範囲の数までをサポートできる。
具体的には、負数を表現する為に補数表現を用いるので最上位のビット(左から1番目の数)を符号代わりに使用する。最上位のビットが0であれば正数、最上位のビットが1なら負数となる。
以下はtinyint型(8ビット)の2進数による補数表現を表にしたもの。
マイナス符号(-)が付く際に、全て0だったビットパターンが反転して全て1になっている。比較表の+126のビットパターンを反転させると-127のビットパターンと同じになることが分かる。補数表現はこのような反転の性質を持つ。
ビット演算子
ビット演算はビット演算子を使用して行う。ビット演算子には以下のようなものがある。
演算子 | 使用例 | 意味 |
---|---|---|
& | a & b | a と b のビットAND |
| | a | b | a と b のビットOR |
^ | a ^ b | a か b のビットXOR |
~ | ~ a | a のビットNOT |
<< | a << b | a を左へ bビット分シフト |
>> | a >> b | a を右へ bビット分シフト |
ビット演算によるフラグ管理
C言語でのビット演算によるフラグ管理を考えてみる。
まずは定数を作る。定数を正数で定義する場合は以下のような対応で各ビットを立たせることができる。※定数として16進数やシフト演算を用いた定義が行われることも多い。
1 = 000001
2 = 000010
4 = 000100
8 = 001000
16 = 010000
32 = 100000
定数の作成は、列挙型のenumやプリプロセッサ命令の#defineで行う。
enum { FLAG_A=1, FLAG_B=2, FLAG_C=4 };
#define FLAG_A 1 //000001
#define FLAG_B 2 //000010
#define FLAG_C 4 //000100
ビット演算子を使用して各ビットを操作する。
/* FLAG_A のビットをオンにする. */
flag |= FLAG_A;
/* FLAG_A と FLAG_B のビットをオンにする. */
flag |= FLAG_A | FLAG_B;
/* FLAG_A のビットをオフにする. */
flag &= ~FLAG_A;
/* FLAG_A と FLAG_B のビットをオフにする. */
flag &= ~(FLAG_A | FLAG_B);
/* FLAG_A と FLAG_B の属性のみを取り出す. (mask) */
flag & (FLAG_A | FLAG_B)
/* FLAG_A のビットだけトグルする. */
flag ^= FLAG_A;
まとめ
各ビットが1つのフラグを表しているので、1つの変数で複数のフラグを管理することが出来る。
所感では、よほどフラグの数が多くない限りは可読性は損なわれると思った。