Packed fields make union type sizes larger in Debug and ReleaseSafe modes?

It looks the alignment of T in the following code is 16:

const std = @import("std");

pub const T = union {
    a: struct {
        q: u32,
    },
    b: packed struct {
        x: u32,
        y: u32,
        z: bool,
    },
};

pub const A = std.meta.FieldType(T, .a);
pub const B = std.meta.FieldType(T, .b);

pub fn main() !void {
    std.debug.print("A: {}\n", .{ @sizeOf(A) }); // 4
    std.debug.print("B: {}\n", .{ @sizeOf(B) }); // 16
    
    std.debug.print("T: {}\n", .{ @sizeOf(T) }); // 32
}

When b becomes unpacked, the sizes become smaller:

const std = @import("std");

pub const T = union {
    a: struct {
        q: u32,
    },
    b: struct {
        x: u32,
        y: u32,
        z: bool,
    },
};

pub const A = std.meta.FieldType(T, .a);
pub const B = std.meta.FieldType(T, .b);

pub fn main() !void {
    std.debug.print("A: {}\n", .{ @sizeOf(A) }); // 4
    std.debug.print("B: {}\n", .{ @sizeOf(B) }); // 12
    
    std.debug.print("T: {}\n", .{ @sizeOf(T) }); // 16
}

Packed structs are backed by integers of equal size, so here your packed struct is backed by a u65.
And @sizeOf(u65) == 16.
This is a bit of a flaw with packed structs. Generally I would avoid odd packed struct sizes like this, or just use extern structs.
In this case you could also fix it by just making the union, and all other entries packed as well.

4 Likes

Thanks for the explanation.

It looks it is my misunderstanding that packed types are always beneficial to memory reducing.

Here is another example. The outputs are consistent for all build modes.
I really want to make Packed union type packed, but it looks this is not supported now.

const std = @import("std");

pub const Unpacked = union(enum) {
    a: struct {
        q: u32,
    },
    b: struct {
        x: u32,
        y: u32,
        z: u32,
        w: u32,
    },
};

pub const Packed = union(enum) {
    a: packed struct {
        q: u32,
    },
    b: packed struct {
        x: u32,
        y: u32,
        z: u32,
        w: u32,
    },
};

pub fn main() !void {
    std.debug.print("Unpacked: {}\n", .{ @sizeOf(Unpacked) }); // 20
    std.debug.print("Packed  : {}\n", .{ @sizeOf(Packed) });   // 32
}

In this example the size ends up at 32 because of alignment. The packed struct has a size of 128 bits and @alignof(u128) == 16, so because of the union tag, this turns into 32 bytes total.

You can avoid this one by explicitly setting the alignment to a lower number.

2 Likes

Thanks again.

I’m optimizing memory consumption for a zig project. Okay, it looks the “packed” way is not the right direction (for specified project).