KDOC 245: パックしたデータ型がどのように保存されているか見る
この文書のステータス
- 作成
- 2024-09-21 貴島
- レビュー
- 2024-10-28 貴島
概要
C言語では、構造体のフィールドにビットフィールドを指定することで、ビット単位の最小サイズを指定しメモリ領域を節約できる。
struct normal {
unsigned bits0_3;
unsigned bits4_11;
unsigned bits12_15;
unsigned bits16_23;
unsigned bits24_31;
};
// コンパイラが割り当てる最小ビット数を指定する
struct packed {
unsigned bits0_3 :4;
unsigned bits4_11 :8;
unsigned bits12_15 :4;
unsigned bits16_23 :8;
unsigned bits24_31 :8;
};
struct normal nor = {1, 2, 3, 4, 5};
struct packed pac = {1, 2, 3, 4, 5};
printf("nor: %zu byte\n",sizeof(nor));
printf("pac: %zu byte\n",sizeof(pac));
nor: 20 byte
pac: 4 byte
アセンブリでどのように保存されているか見る。
;; a: 通常
.L__const.main.a:
.long 1
.long 2
.long 3
.long 4
.long 5
;; b: ビットフィールド指定
.L__const.main.b:
.byte 33
.byte 48
.byte 4
.byte 5
通常の構造体では、すべてlong型で、 1, 2, 3, 4, 5 と格納されているのがわかる。これは、直感に合っている。4バイト(long)にそれぞれ数値がそのまま入っていて、それが5フィールドあるので、合計20バイトを使っているというわけだ。
いっぽう、ビットフィールドを指定すると 33, 48, 4, 5 と奇妙な数値が並んでいる。これは何だろうか。
データセクションのサイズは1バイトが4つで4バイトであり、これは先ほど調べた構造体のサイズと一致している。問題はこの値がどこからきたのか、ということだ。5フィールド分のエントリがないので、明らかに1バイトに複数のフィールドの値が詰められている。
このようにして変換できる(処理系に依る)。
- ↓ 構造体のサイズに基づいて値を配置する{1, 2, 3, 4, 5}
- 0d1 0d2 0d3 0d4 0d5
- |--| |-------| |--| |-------| |-------|
- 0001 0000 0010 0011 0000 0100 0000 0101
- 入れ替え1 構造体の区切りで、上位下位を入れ替える
- |--| |-------| |--| |-------| |-------|
- 0001 0010 0000 0011 0100 0000 0101 0000
- 入れ替え2 1バイトごとで、上位下位を入れ替える
- |-------| |-------| |-------| |-------|
- 0010 0001 0011 0000 0000 0100 0000 0101
- |-0d33--| |-0d48--| |-0d4---| |-0d5---|
このように、ビットフィールドを指定した型を使うことでメモリの使用効率が高まるが、たとえば大小比較がそのままできなくなり元に戻すオーバーヘッドかかるなど計算で不利になるトレードオフがある。
関連
抽象。
- 詰めて保存すると場所は節約できるが、取り出すときに戻す手間がかかる
- 保存された形から、元の値が直感的に読み取れなくなる
関連。
- 動機: KDOC 192: 『Write Great Code Vol.1』の解説を確かめた
- 対比: KDOC 227: フィールド定義の順番によってメモリサイズが変わることがある。保存のしかたが逆向きの例
- 類似: KDOC 229: 浮動小数点を手計算する。値をビット単位の決まった幅に詰めて保存するため、保存された形から元の値がすぐ読めない例