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---|

このように、ビットフィールドを指定した型を使うことでメモリの使用効率が高まるが、たとえば大小比較がそのままできなくなり元に戻すオーバーヘッドかかるなど計算で不利になるトレードオフがある。

関連