オフィスアワーがそろそろ始まるよ!()

データ配置

デフォルトでは、構造体、列挙型、共用体のメモリ配置は保証されていません。すなわち、これらの値がメモリ上で何バイトを占めるのか、構造体のフィールドがどういう順番で配置されるのか、は決まったルールがありません。

default.zenを次の内容で作成します。@byteOffsetOfは、構造体の先頭からフィールドへのバイトオフセットを取得する組込み関数です。

const MyStruct = struct {
    a: u8,
    b: u64,
};

pub fn main() void {
    const a_pos = @byteOffsetOf(MyStruct, "a");
    const b_pos = @byteOffsetOf(MyStruct, "b");
    std.debug.warn("position of a: {}\n", usize(a_pos));
    std.debug.warn("position of b: {}\n", usize(b_pos));
}

MyStructは1バイトのフィールドaと8バイトのフィールドbを持っています。素直に考えるとaのオフセットが0で、bのオフセットが1になりそうなものですが、そうはなりません。このコードを実行すると、各フィールドが配置されている場所が出力されます。

$ zen run default.zen 
position of a: 0
position of b: 8

bMyStructの先頭からオフセット8バイトの位置に配置されています。この結果からわかることは、abとの間に7バイトのパディングが挿入されているということです。場合によっては、フィールドの順番を入れ替えるかもしれません。

Zenではデータのメモリ配置を制御することができます。C ABIと互換性のある形式でメモリ配置したい場合はexternを、一切の順番入れ替えとパディングなしでメモリ配置したい場合はpackedを使用します。

extern

extern struct / extern enum / extern unionのメモリ配置は、C ABIと互換性があることが保証されます。これらのメモリ配置は、C ABIとの互換性が必要な場合のみ利用します。

packed

packed struct / packed enum / packed unionはメモリ配置を明確に定義しています。

packed structは、structと以下の点で異なります。

  • フィールドの順序は宣言順です
  • フィールド間はパディングが挿入されません
  • 任意ビット幅の整数型 (u1など) はそのビット幅の分だけメモリを使用します
  • ブール型のフィールドは1ビットだけメモリを使用します
  • packed enumのフィールドは整数タグ型のビット幅分だけメモリを使用します
  • packed unionのフィールドは最も大きなヴァリアントのビット幅分だけメモリを使用します
  • @bitCastが利用できます

先ほどstructではパディングが挿入されたMyStructpacked structにしてみましょう。今度は、bのバイトオフセットは1になっており、パディングが挿入されていないことがわかります。また、フィールドの順序は宣言順であることも保証されます。

examples/ch11-advanced/memory_layout/src/packed.zen:4:12

const MyStruct = packed struct {
    a: u8,
    b: u64,
};

test "packed struct" {
    ok(@byteOffsetOf(MyStruct, "a") == 0);
    ok(@byteOffsetOf(MyStruct, "b") == 1);
}

packed structはビット幅分だけメモリを利用するため、ビットフィールドに利用できます。次のBitField構造体は、1バイトを3つのビットフィールドに分割しています。

examples/ch11-advanced/memory_layout/src/packed.zen:14:18

const BitField = packed struct {
    a: u1,
    b: u5,
    c: u2,
};

BitField構造体の値は1バイトであることが保証されます。全てのフィールドのバイトオフセットは0になります。より細かなオフセットを知りたい場合は、ビットオフセットを取得する@bitOffsetOfを使用します。各ビットフィールドへのポインタを取得することも可能ですが、特別なアライメントを持つポインタ型になることに注意して下さい。

examples/ch11-advanced/memory_layout/src/packed.zen:20:50

test "bitfield" {
    // `BitField`のサイズは1バイトであることが保証される
    ok(@sizeOf(BitField) == 1);

    // バイトオフセットは全て`0`になる
    ok(@byteOffsetOf(BitField, "a") == 0);
    ok(@byteOffsetOf(BitField, "b") == 0);
    ok(@byteOffsetOf(BitField, "c") == 0);

    // ビットオフセットにより宣言順にパディングなしで
    // 配置されていることがわかる
    ok(@bitOffsetOf(BitField, "a") == 0);
    ok(@bitOffsetOf(BitField, "b") == 1);
    ok(@bitOffsetOf(BitField, "c") == 6);

    const x = BitField {
        .a = 1,
        .b = 2,
        .c = 3,
    };
    // ビットフィールドへのアクセス
    ok(x.a == 1);

    // ビットフィールドのポインタを取得することも可能
    const ptr = &x.b;
    ok(ptr.* == 2);

    // Compile error:
    //   expected '*const u5' type, found '*align(:1:1) const u5'
    // const ng: *const u5 = &x.b;
}

ブール型やpacked enum型のビット幅も保証されるため、ベアメタル / 組込みでの制御レジスタの定義や、通信プロトコルのヘッダ定義などにも便利です。

32ビット (4バイト) の制御レジスタをブール型やpacked enumを使って定義する例は以下のとおりです。

examples/ch11-advanced/memory_layout/src/packed.zen:52:68

pub const GpioPortId = packed enum(u1) {
  Port0 = 0,
  Port1 = 1,
};

const PinSelect = packed struct {
    pin: u5 = 0,
    port: GpioPortId = .Port0,
    reserved: u25 = 0,
    connected: bool = false,
};

test "control register example" {
    ok(@sizeOf(PinSelect) == 4);
    // ビット幅が同じ型から`@bitCast`可能
    const pin: PinSelect = @bitCast(PinSelect, u32(0));
}

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 connectFree Corporation. All rights reserved.
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.