ビットフィールド

出典: フリー百科事典『ウィキペディア(Wikipedia)』

ビットフィールド (: bit field) は、プログラミングにおいてブーリアン型フラグをコンパクトなビットの並びとして格納する手法である。ビットフィールドの格納には、整数型を使用する。個々のフラグは、ビット単位で格納される。通常は、ソースコードで、個別のビットがフラグに対応する意味を付けられた、2の冪乗定数が定義される。ビット演算論理積論理和否定の組み合わせが、フラグのセット・リセットとテストを行うために使われる。

ビットフィールドはビット配列英語版とは異なる。ビット配列は、整数でインデックスを付けられた大きなビットの集合を保存するために使用され、コンピュータ言語でサポートされる整数型よりも大きいことがある。一方、ビットフィールドは典型的にはワードサイズの範囲内であり、各ビットを参照する表記法は数値によるインデックスから独立している。ただし、ビットフィールドを用いるよりも、それぞれのフラグのビットインデックスが列挙型の値であるようなビット配列を使用することで、安全かつ簡潔で、高速に動作する実装となる。

[編集]

C言語での実装例:

#define KEY_UP       (1u << 0u)
#define KEY_RIGHT    (1u << 1u)
#define KEY_DOWN     (1u << 2u)
#define KEY_LEFT     (1u << 3u)
#define KEY_BUTTON1  (1u << 4u)
#define KEY_BUTTON2  (1u << 5u)

unsigned int gameControllerStatus = 0;

void setKeyPressed(unsigned int key) {
    gameControllerStatus |= key;
}

void setKeyReleased(unsigned int key) {
    gameControllerStatus &= ~key;
}

unsigned int isKeyPressed(unsigned int key) {
    return gameControllerStatus & key;
}

C/C++では符号付き整数に対するビット演算は処理系定義の動作となるケースがあるので、符号無し整数を用いるべきである[1]

2の冪乗を表すために0x08のようなハードコードされた数値を使用する代わりに、1 << 3のようなビットシフト演算子を使用した表現を使用すると、ビットマスクを示すものであるというプログラマの意図が明確になり、可読性の観点からすると望ましい。なお、C++14以降では0bプレフィックスを付けることで、0b00001000のような2進数リテラルを記述することもできる[2]。C23にも同様の2進数リテラル表記が導入される予定である[3]

カーニハンリッチーの書籍『プログラミング言語C』では、直接フィールドを定義し、アクセスする方法が記述されている。この方法を使用することで、ビット演算子が不要となり、ビットメンバに構造体メンバと同じようにアクセスすることができる。

struct を使用した例:

struct preferences {
    unsigned int likes_ice_cream : 1;
    unsigned int plays_golf : 1;
    unsigned int watches_tv : 1;
    unsigned int reads_books : 1;
}; 

struct preferences fred;

fred.likes_ice_cream = 1;
fred.plays_golf = 1;
fred.watches_tv = 1;
fred.reads_books = 0;

if (fred.likes_ice_cream == 1) {
    /* ... */
}

構造体のビットメンバは実用上の欠点がある。まず、メモリ上のビットの順序すなわちエンディアンコンパイラ(およびターゲットとなるプロセッサアーキテクチャ)によって変化する[4]。加えて、多くの一般的なコンパイラは、ビットメンバの読み書きに対して、非効率なコードを生成する(プロセッサのワード単位でロード/ストアするほうが効率的)。さらに、ビットフィールドに関しては(特にマルチプロセッサシステムの場合は)、潜在的に重大なスレッドセーフ性の問題がある。ほとんどのCPUではメモリ上の任意のビットの集合を扱えず、最小でもバイト単位のアドレッシングしかできないためである。以下のコード例はミューテックスを使用したとしても、スレッドセーフではない。

struct foo {
    int flag : 1;
    int counter : 15;
};

struct foo my_foo;

pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;

/* あるスレッドで実行される関数 */
void func1(void) {
    pthread_mutex_lock(&my_mutex);
    my_foo.flag = !my_foo.flag;
    pthread_mutex_unlock(&my_mutex);
}

/* 他のスレッドで実行される関数 */
void func2(void) {
    my_foo.counter += 1;
}

ほとんどのCPUでは、flagcounter を別々にロードしてストアすることは、ハードウェアレベルで不可能である。これがスレッドセーフであるためには、counter 自身がスレッドセーフである必要がなくても、「flagcounter の両方」について、全てのアクセスの前後でミューテックスをロック・アンロックする必要がある。同様に、ビット順序によって、ビットフィールドの正確な構造はワードサイズに依存する。

関連項目[編集]

脚注[編集]

外部リンク[編集]