リトルエンディアンの影響かしらないが、ビットフィールドでもそれっぽい影響を受けるらしい。
そんなの習ってねえよ!しらねえよ!
わかりにくい検証とメモ
色々やってみる
4bitであれば、0〜15(0x0〜0xF)を表現できる。
2byte(16bit)分であれば、0〜15の組み合わせを4個準備できる。
上から取得すれば構造体の宣言順に入ると期待し、メモリに0x12,0x34をmemcpyする。
#include <stdio.h> #include <string.h> #define TESTBIT(N) printf("bit%d => %X\n", N, test.bit ## N) int main(){ struct{ unsigned char bit1 : 4; unsigned char bit2 : 4; unsigned char bit3 : 4; unsigned char bit4 : 4; }test; memcpy(&test, "\x12\x34", sizeof(test)); TESTBIT(1); TESTBIT(2); TESTBIT(3); TESTBIT(4); return 0; }
bit1 => 2
bit2 => 1
bit3 => 4
bit4 => 3
ご覧のようになる。
メモリ上には以下のように値が取られている。
アドレス | 0 | 1 | ||
---|---|---|---|---|
数値 | 1 | 2 | 3 | 4 |
2進数値 | 0001 | 0010 | 0011 | 0100 |
おかしいのは構造体のほうで、上から4bitづつ割り当てると、
コンパイラが8bit単位で最適化しようとする影響を受ける。
次の部分を書き換えて実行してみる。
struct{ unsigned char bit1 : 2; unsigned char bit2 : 2; unsigned char bit3 : 2; unsigned char bit4 : 2; }test; memcpy(&test, "\xE4", sizeof(test));
bit1 => 0
bit2 => 1
bit3 => 2
bit4 => 3
2bitは0〜3までの4通りを表現でき、0xE4はその全パターンを含んだ表現。
メモリ上はこのようになる。
アドレス | 0 | |||
---|---|---|---|---|
数値 | 3 | 2 | 1 | 0 |
2進数値 | 11 | 10 | 01 | 00 |
bit1=0 のことから、表では一番右、つまり下位のbitから割り当てられている事がわかる。
しかし、ビットフィールドの長さがバラバラの場合はどうだろうか?
struct{ unsigned char bit1 : 4; unsigned char bit2 : 8; unsigned char bit3 : 4; }test; memcpy(&test, "\x12\x34", sizeof(test)); printf("sizeof(test) = %d\n", sizeof(test)); TESTBIT(1); TESTBIT(2); TESTBIT(3);
sizeof(test) = 3
bit1 => 2
bit2 => 34
bit3 => 0
bit1に4bit, bit2に8bit, bit3に4bit割り当てた。だがこの構造体の大きさは2byteではない。(sizeofの行を参照)
コレはアライメントの影響を受けている。
構造体はアライメントの都合に合わせてサイズを調整される。
アライメントが4であれば、ある構造体が、char, long,でも、char[4],longでも構造体のサイズは8byteになる。
前者はどう考えても5byteなのだが、アライメントが4であれば4byte単位で変数を格納しようとする。
charで1byte消費すると、残り3byte。この中にlong(4byte)は入らないので、次の領域に、となる。
ビットフィールドでも同じ事が起きていて、8bit単位でメンバ変数を格納しようとしているようだ。
メモリは以下のように割り当てられている。
アドレス | 0 | 1 | 2 | |||
---|---|---|---|---|---|---|
数値 | 1 | 2 | 3 | 4 | 0 | 0 |
2進数値 | 0001 | 0010 | 0011 | 0100 | 0000 | 0000 |
bit1は4bitなので、例によって、1byteの下位4bitから割り当てられる。
bit2は8bitなのだが、bit1が4bit専有しており、8bitを割り当てられる領域がない。
よって、次の1byteをbit2に割り当てられる。
bit3はその次の1byteの下位4bitを割り当てられる。
使われない領域は何かしらの方法で代入を行わない限り、何が入るか不定である。
今回は文字列をそのまま代入データにしたのでヌル文字が3byte目に代入されているので。
未検証
8bitを超える組み合わせの場合(例: 3bitと13bitの場合など)
リトルエンディアンが絡んでめんどくさそうだからやりたくない。
まとめ
ビットフィールドで8bit単位以外の領域で割り当てる時、下位bitから順番に割り当てられる。
ビットフィールドでmemcpyなどで操作をする場合は、メンバ変数の宣言の順番に注意が必要。
もちろん、メンバ変数を通して代入を行えば、こういった問題には引っかかりません。
普通はこんなこと意識する必要はないんですけどね。
ビットフィールドを使うよりも、ビット演算で値を出したほうが悩まなくていいかもしれません。