ビットフィールドとメモリの扱い

トルエンディアンの影響かしらないが、ビットフィールドでもそれっぽい影響を受けるらしい。
そんなの習ってねえよ!しらねえよ!


わかりにくい検証とメモ

色々やってみる

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などで操作をする場合は、メンバ変数の宣言の順番に注意が必要。
もちろん、メンバ変数を通して代入を行えば、こういった問題には引っかかりません。


普通はこんなこと意識する必要はないんですけどね。
ビットフィールドを使うよりも、ビット演算で値を出したほうが悩まなくていいかもしれません。