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』。の解説を確かめた