Inconsistent sizeOf vs bitSizeOf

code:

const std = @import("std");
const print = std.debug.print;

const A = packed struct {
    b: B,
    c: C,
};

const B = enum(u8) {
    x,
    y,
    z,
};

const C = packed union {
    x: packed struct {
        foo: u8,
    },
    y: packed struct {
        bar: u8,
    },
    z: packed struct {
        baz: u8,
        qux: u8,
    },
};

pub fn main() !void {
    print("sizeof A: {d} bytes ({d} bits)\n", .{ @sizeOf(A), @bitSizeOf(A) });
    print("sizeof B: {d} bytes ({d} bits)\n", .{ @sizeOf(B), @bitSizeOf(B) });
    print("sizeof C: {d} bytes ({d} bits)\n", .{ @sizeOf(C), @bitSizeOf(C) });

    print(
        "backing int of A: {s}\n",
        .{@typeName(@typeInfo(A).@"struct".backing_integer.?)},
    );
}

output:

sizeof A: 4 bytes (24 bits)
sizeof B: 1 bytes (8 bits)
sizeof C: 2 bytes (16 bits)
backing int of A: u24

Why is @sizeOf(A) 4 bytes, when it is 24 bits and it’s backing integer is a u24? I would expect it to be 3, seeing as everything is packed.

1 Like

From the @sizeOf documentation:

This size may contain padding bytes. If there were two consecutive T in memory, the padding would be the offset in bytes between element at index 0 and the element at index 1.

3 Likes

related topic?

Packed structs are represented in memory using a backing integer (u24 in your case). Access to fields is essentially a convenience feature around bitshifts. This means the backing integer is subject to the same alignment rules as regular integers (u24 will align to 4 bytes). This means you will have padding in the most significant bits (highest memory address for little endian systems, lowest memory address for big endian systems).

Some more reading:

Better documentation about the memory layout of packed structs is needed, but nobody has gotten around to it yet.

3 Likes

Ok, but this is a packed struct so surely there is 0 padding?

This makes sense, so the struct itself is aligned. I think the referenced issue has the answers.

Consider

[2]u24

or

[2]A

What is the offset of the [1] element from the [0] element? That’s what @sizeOf returns; whether or not A is packed is irrelevant. The ‘padding’ in this instance is the extra byte between the [0] element and the [1] element.

If you want a packed array without padding between elements, see the types in std.packed_int_array

2 Likes

I see that now. Thanks.