コンテンツにスキップ

最終更新:2026-10-12

C++ ビットフィールド

著者について:

個人公式アカウント:

wechat

ビットフィールド(Bit field)とは何か?

ビットフィールド(Bit field)とは、データをビット単位でコンパクトに格納できるデータ構造であり、プログラマがこの構造の各ビットを操作することを可能にします。このデータ構造の利点のひとつは、データユニットの記憶領域を節約できることであり、プログラムが何千何万というデータユニットを必要とする場合に特に有効です。もうひとつの利点は、整数値の一部内容に簡単にアクセスでき、プログラムのソースコードを簡潔にできることです。一方、このデータ構造の欠点は、ビットフィールドの実装がマシンやシステムに依存し、プラットフォームが異なれば結果も異なり得ることです。そのためビットフィールドは本質的に移植性がありません。

  • ビットフィールドのメモリ上のレイアウトはマシン依存です
  • ビットフィールドの型は整数型または列挙型でなければならず、符号付き型のビットフィールドの動作は実装依存となります
  • アドレス演算子(&)はビットフィールドに適用できず、どのようなポインタもクラスのビットフィールドを指すことはできません

ビットフィールドの使用

ビットフィールドは通常、構造体を使って宣言します。この構造体宣言では、各ビットフィールドメンバに名前を付け、その幅を決定します:

struct bit_field_name
{
    type member_name : width;
};
Elements 説明
bit_field_name ビットフィールド構造体の名前
type ビットフィールドメンバの型。int、signed int または unsigned int 型でなければならない
member_name ビットフィールドメンバの名前
width メンバが占めるビット数を規定します

例えば、次のようなビットフィールドを宣言します:

struct _PRCODE
{
    unsigned int code1: 2;
    unsigned int cdde2: 2;
    unsigned int code3: 8;
};
struct _PRCODE prcode;

この定義により、prcodeは2ビットのビットフィールド2つと8ビットのビットフィールド1つを含むことになります。構造体のメンバアクセス演算子を使って代入できます。

prcode.code1 = 0;
prcode.code2 = 3;
procde.code3 = 102;

代入するときは、値の大きさがビットフィールドメンバの容量を超えないように注意してください。例えば prcode.code3 は8ビットのビットフィールドメンバであり、その容量は 2^8 = 256、すなわち代入範囲は [0,255] です。

ビットフィールドのサイズとアライメント

ビットフィールドのサイズ

例えば、以下のビットフィールドです:

struct box 
{
    unsigned int a: 1;
    unsigned int  : 3;
    unsigned int b: 4;
};

このビットフィールド構造体の途中には、3ビットを占める名前なしのビットフィールドがあり、詰め物としてのみ機能し、実際の意味はありません。この詰め物により、構造体全体で8ビットを使用します。しかしC言語では unsigned int をビットフィールドの基本単位として扱うため、構造体の唯一のメンバが1ビットのビットフィールドであっても、その構造体のサイズは unsigned int 1つ分と同じになります。システムによっては unsigned int が16ビットのこともあり、x86システムでは32ビットです。本記事では以降、unsigned int を32ビットとして扱います。

ビットフィールドのアライメント

ひとつのビットフィールドメンバが2つの unsigned int の境界をまたぐことは許されません。メンバの宣言ビット数の合計が unsigned int 1つ分のサイズを超える場合、コンパイラはビットフィールドメンバを自動的に移動させ、unsigned int の境界に合わせてアラインします。 例えば:

struct stuff 
{
    unsigned int field1: 30;
    unsigned int field2: 4;
    unsigned int field3: 3;
};

field1 + field2 = 34ビットとなり、32ビットを超えるため、コンパイラはfield2を次の unsigned int 単位へ移動して格納します。stuff.field1 と stuff.field2 の間には2ビットの空きができ、stuff.field3 は stuff.field2 の直後に続きます。この構造体のサイズは現在 2 * 32 = 64ビットです。

この空きは、先に述べた名前なしのビットフィールドメンバで埋めることができます。また、幅0の名前なしビットフィールドメンバを使えば、次のビットフィールドメンバを次の整数境界に合わせることもできます。 例えば:

struct stuff 
{
    unsigned int field1: 30;
    unsigned int       : 2;
    unsigned int field2: 4;
    unsigned int       : 0;
    unsigned int field3: 3; 
};

ここでは stuff.field1 と stuff.field2 の間に2ビットの空きがあり、stuff.field3 は次の unsigned int に格納されます。この構造体のサイズは現在 3 * 32 = 96ビットです。

学習用コードはこちら:bit.cpp

ビットフィールドの初期化とビットの再マッピング

初期化

ビットフィールドの初期化は、通常の構造体の初期化方法と同じです。ここでは2つの方法を示します:

struct stuff s1= {20,8,6};

または、直接ビットフィールドメンバに値を代入します

struct stuff s1;
s1.field1 = 20;
s1.field2 = 8;
s1.field3 = 4;

ビットフィールドの再マッピング (Re-mapping)

サイズが32ビットのビットフィールドを宣言します

struct box {
    unsigned int ready:     2;
    unsigned int error:     2;
    unsigned int command:   4;
    unsigned int sector_no: 24;
}b1;

再マッピングによるビットフィールドのゼロクリア

int* p = (int *) &b1;  // 将 "位域结构体的地址" 映射至 "整形(int*) 的地址" 
*p = 0;                // 清除 s1,将各成员归零

共用体(union)による32ビットのビットフィールドから unsigned int 型への再マッピング

まず共用体について簡単に紹介します

「共用体」は特殊なクラスであり、構造的なデータ型でもあります。ひとつの「共用体」の中には複数の異なるデータ型を定義でき、その「共用体」型として宣言された変数には、「共用体」で定義されたどのデータでも格納できます。これらのデータは同一段のメモリを共有し、空間の節約を実現します

「共用体」と「構造体」にはいくつか似た点がありますが、両者には本質的な違いがあります。構造体では各メンバがそれぞれのメモリ空間を持ち、構造体変数の全長は各メンバの長さの和です(空の構造体は除く。また境界調整は考慮しません)。一方「共用体」では、各メンバが同一段のメモリ空間を共有し、共用体変数の長さは各メンバの中で最も長いものと等しくなります。なお、ここでいう共有とは、複数のメンバを同時にひとつの共用体変数に格納することではなく、その共用体変数にどのメンバの値でも代入できることを指します。ただし毎回代入できるのは1種類の値だけであり、新しい値を代入すると古い値は上書きされます。

以下のような共用体を宣言できます:

union u_box {
  struct box st_box;     
  unsigned int ui_box;
};

x86システムでは unsigned int も box も32ビットであり、この共用体によって st_box と ui_box は同一のメモリを共有します。ビットフィールドのどのビットが unsigned int のどのビットに対応するかは、コンパイラとハードウェアによって決まります。 共用体を使ってビットフィールドをゼロにするコードは以下の通りです:

union u_box u;
u.ui_box = 0;

学習参考:http://www.yuan-ji.me/C-C-%E4%BD%8D%E5%9F%9F-Bit-fields-%E5%AD%A6%E4%B9%A0%E5%BF%83%E5%BE%97/

関連トピック

コメント