導入
前のレッスンでは #define とビット演算を自分で組み合わせてフラグを管理しました。C言語には、構造体のメンバごとに「使うビット数」を直接指定できるビットフィールドという機能があり、切り詰めをコンパイラに任せることができます。
説明
構造体のメンバ名の後に : 幅 と書くと、そのメンバが使うビット数を指定できます。
struct Flags {
unsigned int poison : 1; // 1ビット(0か1)
unsigned int stun : 1; // 1ビット
unsigned int invisible : 1; // 1ビット
unsigned int level : 5; // 5ビット(0〜31を表せる)
};
通常、unsigned int のメンバを4つ並べると16バイト使いますが、ビットフィールドを使うと必要なビット数だけを詰め込んで確保するので、struct Flags はずっと少ないバイト数(多くの処理系では unsigned int 1個分)に収まります。実際のバイト数は sizeof で確認できます。
flowchart LR
subgraph unit["1つの unsigned int(32ビット)の中"]
direction LR
lv["level: 5bit"]
inv["invisible: 1bit"]
st["stun: 1bit"]
po["poison: 1bit"]
unused["残りは未使用"]
end
#include <stdio.h>
struct Flags {
unsigned int poison : 1;
unsigned int stun : 1;
unsigned int invisible : 1;
unsigned int level : 5;
};
int main(void) {
struct Flags f = {0};
f.poison = 1;
f.level = 12;
printf("poison=%u stun=%u invisible=%u level=%u\n", f.poison, f.stun, f.invisible, f.level);
printf("structのサイズ: %zu バイト\n", sizeof(struct Flags));
f.stun = 1;
if (f.stun) {
printf("スタン状態です\n");
}
f.level = 40; // 5ビットは0〜31までしか表せない
printf("31を超えた40を入れると: level=%u\n", f.level);
return 0;
}
最後の部分に注目してください。level は5ビットしかないので、表せる範囲は0〜31です。40(2進数で 101000、6ビット必要)を入れると、はみ出した上位のビットは失われ、下位5ビット分(01000 = 8)だけが残ります。これは前のレッスンで見た「マスクで下位ビットだけ取り出す」操作と同じことが、代入時に自動で起きているとイメージすると理解しやすいです。
やってみよう
各メンバのビット数を変えて、sizeof(struct Flags) の結果がどう変わるか確認してみましょう(合計ビット数が32を超えると、コンパイラはさらに大きな領域を確保します)。f.level = 40; の 40 を 50 や 100 に変えて、level の値がどうなるか予想してから実行してみてください。
演習
unsigned int a : 2; と unsigned int b : 6; を持つビットフィールド構造体を定義し、a に 3、b に 10 を代入して、両方の値を表示してください。
ヒント1を見る
struct X { unsigned int a : 2; unsigned int b : 6; }; のように定義します。
ヒント2を見る
struct X x = {0}; x.a = 3; x.b = 10; printf("a=%u b=%u\n", x.a, x.b);