ビットフィールドとは
C/C++にはほとんど使われてないがビットフィールドという機能がある。
union { uint8_t data; struct { unsigned FAULT_QUEUE : 2; unsigned CT_PIN_POLARITY : 1; unsigned INT_PIN_POLARITY : 1; unsigned INT_CT_MODE : 1; unsigned OPERATION_MODE : 2; unsigned RESOLUTION : 1; }; } config;
このように書ける。struct 内で名前の後ろについているのが、そのフィールドで消費するビット数で、この場合合計で8bitになり、それを uint8_t と共用している。
こうすると config.OPERATION_MODE = 2; などと、マスクやシフトを伴わずに直接書けて、結果をconfig.rawでとれる。
めっちゃ便利なので使わない手はなさそうだと思いきや、実際のところ実用するのは不安がある。というのも、この struct 内のビット配置の順序は実装依存となっていて、uint8_t として評価したとき、どのような結果が返ってくるか確かなことがいえない。
コンパイラ依存
再発明
そこで、上記のようなビットフィールドを以下のように書きなおす
template <class T, uint8_t s, uint8_t e = s> struct bits { T ref; static constexpr T mask = (T)(~( (T)(~0) << (e - s + 1))) << s; void operator=(const T val) { ref = (ref & ~mask) | ((val & (mask >> s)) << s); } operator T() const { return (ref & mask) >> s; } }; template <uint8_t s, uint8_t e = s> using bits8 = bits<uint8_t, s, e>; union { uint8_t data = 0; bits8<0, 1> FAULT_QUEUE ; bits8<2> CT_PIN_POLARITY ; bits8<3> INT_PIN_POLARITY ; bits8<4> INT_CT_MODE ; bits8<5, 6> OPERATION_MODE ; bits8<7> RESOLUTION ; } config;
uint8_t 全体を明確に共用する複数のstructという形にし、明示的にビットシフトやマスクを行っている。それぞれ、テンプレートの第一引数〜第二引数のビットを扱うクラスになっている。
用途
組み込みで他のデジタルICとやりとりをする場合、だいたいデータシートには [0:1] foobar みたいな形でビット範囲と値の説明が書いてあるので、それをその通り書きうつして union を作れば間違いなくビット操作できる状態になる。
これで安心してビットフィールドっぽいものが使える。
生成バイナリ
試した限りだと完全にインライン化される。また、1bitだけ書く場合andかorだけにまで最適化される。