Merge pull request #21741 from kj4tmp/langref-packed-structs

langref: improve packed struct memory layout description
This commit is contained in:
Andrew Kelley 2025-04-16 04:56:22 -04:00 committed by GitHub
commit 3746b3d93c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 44 additions and 19 deletions

View File

@ -1649,6 +1649,7 @@ unwrapped == 1234{#endsyntax#}</pre>
<li>{#link|Floats#}</li>
<li>{#link|bool|Primitive Types#}</li>
<li>{#link|type|Primitive Types#}</li>
<li>{#link|packed struct#}</li>
</ul>
</td>
<td>
@ -2224,20 +2225,24 @@ or
{#header_open|packed struct#}
<p>
Unlike normal structs, {#syntax#}packed{#endsyntax#} structs have guaranteed in-memory layout:
{#syntax#}packed{#endsyntax#} structs, like {#syntax#}enum{#endsyntax#}, are based on the concept
of interpreting integers differently. All packed structs have a <strong>backing integer</strong>,
which is implicitly determined by the total bit count of fields, or explicitly specified.
Packed structs have well-defined memory layout - exactly the same ABI as their backing integer.
</p>
<p>
Each field of a packed struct is interpreted as a logical sequence of bits, arranged from
least to most significant. Allowed field types:
</p>
<ul>
<li>Fields remain in the order declared, least to most significant.</li>
<li>There is no padding between fields.</li>
<li>Zig supports arbitrary width {#link|Integers#} and although normally, integers with fewer
than 8 bits will still use 1 byte of memory, in packed structs, they use
exactly their bit width.
</li>
<li>{#syntax#}bool{#endsyntax#} fields use exactly 1 bit.</li>
<li>An {#link|integer|Integers#} field uses exactly as many bits as its
bit width. For example, a {#syntax#}u5{#endsyntax#} will use 5 bits of
the backing integer.</li>
<li>A {#link|bool|Primitive Types#} field uses exactly 1 bit.</li>
<li>An {#link|enum#} field uses exactly the bit width of its integer tag type.</li>
<li>A {#link|packed union#} field uses exactly the bit width of the union field with
the largest bit width.</li>
<li>Packed structs support equality operators.</li>
<li>A {#syntax#}packed struct{#endsyntax#} field uses the bits of its backing integer.</li>
</ul>
<p>
This means that a {#syntax#}packed struct{#endsyntax#} can participate
@ -2245,10 +2250,11 @@ or
This even works at {#link|comptime#}:
</p>
{#code|test_packed_structs.zig#}
<p>
The backing integer is inferred from the fields' total bit width.
Optionally, it can be explicitly provided and enforced at compile time:
The backing integer can be inferred or explicitly provided. When
inferred, it will be unsigned. When explicitly provided, its bit width
will be enforced at compile time to exactly match the total bit width of
the fields:
</p>
{#code|test_missized_packed_struct.zig#}
@ -2290,18 +2296,18 @@ or
<p>
Equating packed structs results in a comparison of the backing integer,
and only works for the `==` and `!=` operators.
and only works for the {#syntax#}=={#endsyntax#} and {#syntax#}!={#endsyntax#} {#link|Operators#}.
</p>
{#code|test_packed_struct_equality.zig#}
<p>
Using packed structs with {#link|volatile#} is problematic, and may be a compile error in the future.
For details on this subscribe to
<a href="https://github.com/ziglang/zig/issues/1761">this issue</a>.
TODO update these docs with a recommendation on how to use packed structs with MMIO
(the use case for volatile packed structs) once this issue is resolved.
Don't worry, there will be a good solution for this use case in zig.
Field access and assignment can be understood as shorthand for bitshifts
on the backing integer. These operations are not {#link|atomic|Atomics#},
so beware using field access syntax when combined with memory-mapped
input-output (MMIO). Instead of field access on {#link|volatile#} {#link|Pointers#},
construct a fully-formed new value first, then write that value to the volatile pointer.
</p>
{#code|packed_struct_mmio.zig#}
{#header_close#}
{#header_open|Struct Naming#}

View File

@ -0,0 +1,19 @@
pub const GpioRegister = packed struct(u8) {
GPIO0: bool,
GPIO1: bool,
GPIO2: bool,
GPIO3: bool,
reserved: u4 = 0,
};
const gpio: *volatile GpioRegister = @ptrFromInt(0x0123);
pub fn writeToGpio(new_states: GpioRegister) void {
// Example of what not to do:
// BAD! gpio.GPIO0 = true; BAD!
// Instead, do this:
gpio.* = new_states;
}
// syntax