C言語 ビットフィールドを使ったビット単位のパッケージ方法紹介

C言語
この記事は約8分で読めます。

こんにちは、ナナです。

構造体の仲間であるビットフィールド機能を紹介しましょう。

ビットフィールドは構造体の仕組みを利用するため、構造体に関する知識は必ず押さえておきましょう。次の記事を参考にしてください。

そして、ビットフィールドの名の通り、ビットに関する知識が不可欠です。ビット演算についての知識も押さえておく必要があります。こちらの記事を参考にしてください。

本記事では次の疑問点を解消する内容となっています。

本記事で学習できること
  • ビットフィールドってどういう機能なの?
  • ビットフィールドとビット演算との関係性は?
  • ビットフィールドを使ったデータアクセス方法とは?
  • ビットフィールドを使う時に知っておくべき注意点とは?
  • ビット演算 vs ビットフィールド その結末とは?

では、ビットフィールドの使い方を学んでいきましょう。

スポンサー

ビットフィールド登場。ビット操作の魔術師リストラ危機?

魔術師

なんだこの記事は、解せんタイトルであるな。わしが臨時講師をリストラじゃと?お前をこの魔術で消してやろうかっ!

ナナ

タイトルをよく見てください。「リストラの危機?」ですから、まだわかりません。そんな会議がされているようですが、まだあきらめないでください。

ビットフィールド機能は、ビット演算のようにビットに関する制御を行うための機能です。

はたして、「ビット操作の魔術師 vs ビットフィールド」どちらが生き残るのでしょうか?

スポンサー

ビットフィールドの定義方法

魔術師

わしの目自ら確かめてやろう!貴様っ、ビットフィールドとやらを説明してみよ。

ナナ

それではまず、ビットフィールドを利用するための定義方法から説明します。構造体定義と似ていますが、各構造体メンバにビット数を割り付けるのが異なります。

ビットフィールドは少し特殊な構造体の形として定義します。やはり、ビットフィールドもtypedefを利用して定義するのが一般的です。

まずは、ビットフィールド構造体の型定義をプログラムで示しましょう。

// ビットフィールド構造体の型定義
typedef struct
{
    unsigned char man : 1; // 性別
    unsigned char num : 5; // 出席番号
    unsigned char cls : 2; // クラス
} S_STUDENT;

構造体メンバの右側に数値が書かれています。これは構造体メンバに割り付けたビット数です。

3つ合わせて合計8ビットになっていますね。unsigned char型は1バイトのため、そのビット数に合わせています。

ビットフィールドとして割り付けたビット数の意味

ビットフィールドとして割り付けた各ビットは、次のように1バイトデータと関連付きます。

このように、ビットフィールドはビットレベルで構造体メンバを定義する機能を持っています。

スポンサー

ビットフィールドを使ったデータアクセス方法

魔術師

ふむ、言うだけのことはあるのう。ここまでは善戦しとるぞ。で、こやつはいったいどうやってビットを読み書きするのじゃ?それを見んと、まだ認められんの。

ナナ

では、続いてビットフィールドを使ったデータの読み書きについて解説します。初心者にやさしい方法が用意されています。

ビットフィールドを使ったプログラム

ビットフィールドは構造体として定義を行います。次のように構造体メンバに対して割り当てるビット数を明記することで行います。

#include <stdio.h>

// ビットフィールド構造体定義
typedef struct
{
    unsigned char man : 1; // 性別
    unsigned char num : 5; // 出席番号
    unsigned char cls : 2; // クラス
} S_STUDENT;

int main(void)
{
    // ビットフィールド構造体の変数定義
    S_STUDENT   studentA;

    // 構造体メンバへの書き込み
    studentA.cls = 2;  // Cクラス
    studentA.num = 15; // 15番
    studentA.man = 1;  // 男性

    // 構造体メンバの読み取り
    printf("%d %d %d", studentA.cls, studentA.num, studentA.man);

    return 0;
}

ビットフィールドの構造体メンバは、構造体と同様のアクセスが可能です。設定すべき値はビット数で表現できる範囲にします。

もし、ビット数よりも大きな数字を書き込んだ場合は、オーバーフローと同じ動作となります。

魔術師

ぬぬぬ、代入でビット操作ができるだと!貴様っ、わしを廃業に追い込むつもりかっ!こんな機能は絶対に認めんぞ、絶対にだ!

スポンサー

ビットフィールドに関する注意点

魔術師

術というのは強力であるほど、そのリスクも大きいものなんじゃ。これほどの機能にそれがないとは言わせんぞ。あるじゃろ大きなリスクが、隠すなよ!

ナナ

実は…あります。ビットフィールドは実際の開発現場では、あまり使われないことも多い機能なんです。理由は環境依存性が高いからです。

S_STUDENTの構造体メンバの並びが、イメージ図とは逆順になっていることに気づきましたか?

// ビットフィールド構造体定義
typedef struct
{
    // 下位ビットから順にバイトデータに割り付け
    // ただし、環境依存で変化する可能性あり
    unsigned char man : 1; // ③:性別
    unsigned char num : 5; // ②:出席番号
    unsigned char cls : 2; // ①:クラス
} S_STUDENT;

WindowsのVisual Studioの環境では、定義した順に下位ビットから割り付けられます

しかし、このビット割り付けの並び順は環境によって上から割り付けるか、下から割り付けるかが変化する可能性があります。

組み込み開発では様々なマイコンで動作させるため、プログラムの移植性が重要視されます。環境により上下が変わるようなビットフィールドは、使用を避けられる傾向があります。

これこそがビットフィールド最大の弱点です。

魔術師

ファ、ファ、ファ、そうじゃろそうじゃろ。万能の術などないのじゃよ。

スポンサー

Q&A:共用体でよくある質問

ナナ

魔術師講師、ビットフィールドに関する質問お答えます。

Q:ビットフィールドはlong型などでも使えるんでしょうか?

魔術師

この機能、char型以外にもshort型やlong型のメンバにも使えると思ってよいのか?それくらいできなければ話にならんがな。

ナナ

はい。もちろん可能です。ビットの数が変わりますので適宜割り付けるビット数を変化せる必要があります。

構造体メンバに対する変数の型は、long型など自由に選択できます。

#include <stdio.h>

typedef struct
{
    unsigned long num1 : 5;
    unsigned long num2 : 17;
    unsigned long num3 : 4;
    unsigned long num4 : 6;
} S_SAMPLE;

int main(void)
{
    S_SAMPLE sample;

    sample.num1 = 28;
    sample.num2 = 30000;
    sample.num3 = 15;
    sample.num4 = 50;

    printf("%d %d %d %d", sample.num1, sample.num2, sample.num3, sample.num4);

    return 0;
}

表示結果は次のものになります。ビット数に応じて記憶できる範囲が変わっていますね。

28 30000 15 50

Q:ビット演算とビットフィールドは結局どっちがいいの?

魔術師

おい、貴様。この戦いにどう終止符を打つつもりじゃ。貴様の見解を聞かせいぃ!事と次第によってはわかっておるであろうな?
わしのローン支払いの命運が掛かっておるのだぞ!

ナナ

重い…。この戦いの結論にそれほどの価値があるのかと…。

ビットフィールドは、初心者にとっては代入処理や比較処理など、通常の変数のように扱えるという点で優秀です。

しかし、環境依存性が高く、また、ビットに情報を割り付けるシーンにおいて、わざわざビットフィールド構造体を定義しなければならないという点は結構マイナス要素があるんです。

というわけで、現実的にはビット演算の需要はいまだ健在です。めでたし、めでたし。

魔術師

よかろう、わしもまだまだ現役じゃ。貴様を消すのはやめておこう!