C++ でビットフィールドを再発明する
ビットフィールドとは
C/C++にはほとんど使われてないがビットフィールドという機能がある。
union {
uint8_t raw;
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 raw = 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だけにまで最適化される。
int main(void) {
asm volatile ("nop");
config.OPERATION_MODE = 0b11;
asm volatile ("nop");
config.RESOLUTION = 1;
asm volatile ("nop");
config.FAULT_QUEUE = 1;
asm volatile ("nop");
for (;;) {
}
return 0;
}
こういうコードは
000000a0 <main>: a0: 00 00 nop a2: 00 00 nop a4: 00 00 nop a6: 80 91 00 01 lds r24, 0x0100 aa: 8c 71 andi r24, 0x1C ; 28 ac: 81 6e ori r24, 0xE1 ; 225 ae: 80 93 00 01 sts 0x0100, r24 b2: 00 00 nop b4: ff cf rjmp .-2 ; 0xb4 <main+0x14>
こうなる
テスト
| #include <cstdio> | |
| #include <stdint.h> | |
| #include <iostream> | |
| template <class T, class U> | |
| void is(T got, U expected) { | |
| if (got == expected) { | |
| std::cout << "ok" << std::endl; | |
| } else { | |
| std::cout << "not ok " << got << " != " << expected << std::endl; | |
| } | |
| } | |
| 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>; | |
| int main () { | |
| union { | |
| uint8_t raw = 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; | |
| config.OPERATION_MODE = 0b11; | |
| is((uint)config.raw, 0b01100000); | |
| config.FAULT_QUEUE = 0b10; | |
| is((uint)config.raw, 0b01100010); | |
| config.RESOLUTION = 1; | |
| is((uint)config.raw, 0b11100010); | |
| config.OPERATION_MODE = 0; | |
| is((uint)config.raw, 0b10000010); | |
| config.raw = 0; | |
| is((uint)config.OPERATION_MODE, 0b00); | |
| config.raw = 0b01000000; | |
| is((uint)config.OPERATION_MODE, 0b10); | |
| config.FAULT_QUEUE = 0b111; | |
| is((uint)config.raw, 0b01000011); | |
| return 0; | |
| } |
関連エントリー
- 任意固定小数点→浮動小数点変換スニペット I2Cセンサーとかを扱うと固定小数点表現によく出会う。が、固定小数点のままだと計算がめんどうなので、とりあえず浮動小数点に変換しときたいとい...
- AD9851 DDS モジュール AD9850 DDS モジュール に続き AD9851 で、別基板バージョンのものです。 ジャンパとかが一切ない簡略版?なのか進化版?でしょ...
- ARM Linux EABI の asm で簡単な ls を作る 単に実行したディレクトリのファイル名を表示するだけのプログラムを asm で書いてみる。 普段全くディレクトリエントリの構造を意識しないけど...
- AD9851 DDS モジュールを LPC1114/mbed と AD9851 は Arduino で一度動かしてみましたが、LPC1114/mbed な環境でも動かしてみました。 DDS モジュールの定格...
- AD9850 DDS モジュール ebay で800円ぐらいで買ったものです。 この手のモジュールにはAD9851(源クロック6倍周波数逓倍器付き)のものとAD9850のもの...