本文へスキップ
BecomeCoder

C言語コース · 第12章 ビット操作 · レッスン48

ビットフィールド構造体 ― メモリを切り詰めて使う

ブラウザで完結

導入

前のレッスンでは #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です。402進数101000、6ビット必要)を入れると、はみ出した上位のビットは失われ、下位5ビット分(01000 = 8)だけが残ります。これは前のレッスンで見た「マスクで下位ビットだけ取り出す」操作と同じことが、代入時に自動で起きているとイメージすると理解しやすいです。

やってみよう

各メンバのビット数を変えて、sizeof(struct Flags) の結果がどう変わるか確認してみましょう(合計ビット数が32を超えると、コンパイラはさらに大きな領域を確保します)。f.level = 40;4050100 に変えて、level の値がどうなるか予想してから実行してみてください。

演習

unsigned int a : 2;unsigned int b : 6; を持つビットフィールド構造体を定義し、a3b10 を代入して、両方の値を表示してください。

ヒント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);

実際に動かしてみよう

下のエディタにCを書いて「コンパイル & 実行」を押すと、ブラウザ内で本物のCコンパイラ(clang + dlmalloc)がその場でコンパイルして実行し、出力が表示されます。構造体・共用体・malloc/free・関数ポインタ・ファイル入出力などがそのまま動きます。本文の例を書き換えて試しながら進めましょう(初回だけコンパイラの読み込みに時間がかかります。以降はキャッシュされます。この回は標準入力(scanf)は使わず、値はコードに直接書きます)。

C/C++ — ブラウザ内で本物のコンパイラで実行

ブラウザ内で本物のC/C++コンパイラ(clang + libc++ / WebAssembly)を動かします。構造体・malloc/free・std::string・vector・クラス・テンプレート・STL などがそのまま実行できます(初回のみコンパイラの読み込みに時間がかかります。以降はブラウザにキャッシュされます)。
スクロールして表示された時点でも自動で読み込まれます。