Wrote this blog post to summarize what I think are the right ways to understand alignment and size for various data types in Zig, just through experimentation.
Let me know any and all feedback!
if you need a well defined layout, use `extern`. if your struct makes sense to represent as an integer, use `packed`. I think it is often ill advisable to use `packed` otherwise.
you can explore this yourself on the Type info returned from @TypeInfo(T):
https://ziglang.org/documentation/master/std/#std.builtin.Ty...
https://ziglang.org/documentation/master/std/#std.builtin.Ty...
https://ziglang.org/documentation/master/std/#std.builtin.Ty...
> An extern struct has in-memory layout matching the C ABI for the target.
Zig is really good at speaking the C ABI of the target, but the upshot seems to be that it appears there is no stable Zig-native ABI.
If I'm correct, I wonder if there are plans to settle on a stable ABI at some point in the future. I do know that in other languages the lack of a stable ABI is brought up as a downside, and although I've been burned by C++ ABI stability too many times to agree, I can understand why people would want one.
Heres the kind of code it generates https://zigbin.io/6dba68
It can also generate javascript, heres doom running on browser: https://cloudef.pw/sorvi/#doom.wasm
That could be an interesting middle ground.
struct Dang : bits 64 // 64 bits wide, int total
{
foo : bits 5 @ 0; // 5 bits wide at bit offset 0
bar : bits 5 @ 0;
baz : bits 16 @ 4; // 16 bits wide at bit offset 4
tom : bits 11 @ 32;
};https://godbolt.org/z/vPKEdnjan
union Dang
{
uint64_t : 64; // set total width
uint8_t foo : 5;
uint8_t bar : 5;
struct __attribute__((packed)) {
uint8_t : 4;
uint16_t baz : 16;
};
struct __attribute__((packed)) {
uint32_t : 32;
uint16_t tom : 11;
};
};
The member types don't actually matter here so we can have a little fun and macro it without having to resort to templates to get "correct" types. #define OFFSET_BITFIELD_DECLARE(NAME, SIZE) \
union NAME { \
uint64_t : SIZE
#define BITFIELD_MEMBER(NAME, SIZE, OFFSET) \
struct __attribute__((packed)) { \
uint64_t : OFFSET; \
uint64_t NAME : SIZE; \
}
#define OFFSET_BITFIELD_END() }
OFFSET_BITFIELD_DECLARE(Dang, 64);
BITFIELD_MEMBER(foo, 5, 0);
BITFIELD_MEMBER(bar, 5, 0);
BITFIELD_MEMBER(baz, 16, 4);
BITFIELD_MEMBER(tom, 11, 32);
OFFSET_BITFIELD_END();
Highly recommend not doing this in production code. If nothing else, there's no compiler protection against offset+size being > total size, but one could add it with a static assert! (I've done so in the godbolt link)Edit: if you're talking about Zig, sorry!
https://arxiv.org/abs/2410.11094
I'm not sure I understand your example; if I am looking at it right, it has overlapping bitfields.
But supposing you didn't want overlapping fields, you could write:
type Dang(tom: u11, baz: u16, bar: u5, foo: u5) #packed;
And the compiler would smash the bits together (highest order bits first).If you wanted more control, you can specify where every bit of every field goes using a bit pattern:
type Dang(tom: u11, baz: u16, bar: u5, foo: u5) #packed 0bTTTTTTTT_TTTbbbbb_bbbbbbbb_bbbzzzzz_????fffff
Where each of T, b, z, and r represent a bit of each respective field.https://arxiv.org/abs/2410.11094
Bradley implemented a prototype of the packing solver, but it doesn't do the full generality of what is proposed in the paper.
It can even be used for pattern matching.
I don't know whether Gleam or Elixir inherited it.