docs for packed structs

closes #1513
This commit is contained in:
Andrew Kelley 2019-02-22 10:56:49 -05:00
parent 0c5f897904
commit d0c39895aa
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9

View File

@ -8,13 +8,7 @@
body{
background-color:#111;
color: #bbb;
font-family: system-ui,
/* Fallbacks for browsers that don't support system-ui */
/* https://caniuse.com/#search=system-ui */
-apple-system, /* iOS and macOS */
Roboto, /* Android */
"Segoe UI", /* Windows */
sans-serif;
font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif;
}
a {
color: #88f;
@ -707,15 +701,21 @@ fn divide(a: i32, b: i32) i32 {
{#code_end#}
<p>
In this function, values {#syntax#}a{#endsyntax#} and {#syntax#}b{#endsyntax#} are known only at runtime,
and thus this division operation is vulnerable to both integer overflow and
division by zero.
and thus this division operation is vulnerable to both {#link|Integer Overflow#} and
{#link|Division by Zero#}.
</p>
<p>
Operators such as {#syntax#}+{#endsyntax#} and {#syntax#}-{#endsyntax#} cause undefined behavior on
integer overflow. Also available are operations such as {#syntax#}+%{#endsyntax#} and
{#syntax#}-%{#endsyntax#} which are defined to have wrapping arithmetic on all targets.
</p>
{#see_also|Integer Overflow|Division by Zero|Wrapping Operations#}
<p>
Zig supports arbitrary bit-width integers, referenced by using
an identifier of <code>i</code> or </code>u</code> followed by digits. For example, the identifier
{#syntax#}i7{#endsyntax#} refers to a signed 7-bit integer. The maximum allowed bit-width of an
integer type is {#syntax#}65535{#endsyntax#}.
</p>
{#see_also|Wrapping Operations#}
{#header_close#}
{#header_close#}
{#header_open|Floats#}
@ -2047,13 +2047,203 @@ test "linked list" {
}
{#code_end#}
{#header_open|packed struct#}
<p>{#syntax#}packed{#endsyntax#} structs have guaranteed in-memory layout.</p>
<p>TODO bit fields</p>
<p>TODO alignment</p>
<p>TODO endianness</p>
<p>TODO @bitOffsetOf and @byteOffsetOf</p>
<p>TODO mention how volatile loads and stores of bit packed fields could be more efficient when
done by hand instead of with packed struct</p>
<p>
Unlike normal structs, {#syntax#}packed{#endsyntax#} structs have guaranteed in-memory layout:
</p>
<ul>
<li>Fields remain in the order declared.</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>A {#link|packed 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>Non-byte-aligned fields are packed into the smallest possible
byte-aligned integers in accordance with the target endianness.
</li>
</ul>
<p>
This means that a {#syntax#}packed struct{#endsyntax#} can participate
in a {#link|@bitCast#} or a {#link|@ptrCast#} to reinterpret memory.
This even works at {#link|comptime#}:
</p>
{#code_begin|test#}
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Full = packed struct {
number: u16,
};
const Divided = packed struct {
half1: u8,
quarter3: u4,
quarter4: u4,
};
test "@bitCast between packed structs" {
doTheTest();
comptime doTheTest();
}
fn doTheTest() void {
assert(@sizeOf(Full) == 2);
assert(@sizeOf(Divided) == 2);
var full = Full{ .number = 0x1234 };
var divided = @bitCast(Divided, full);
switch (builtin.endian) {
builtin.Endian.Big => {
assert(divided.half1 == 0x12);
assert(divided.quarter3 == 0x3);
assert(divided.quarter4 == 0x4);
},
builtin.Endian.Little => {
assert(divided.half1 == 0x34);
assert(divided.quarter3 == 0x2);
assert(divided.quarter4 == 0x1);
},
}
}
{#code_end#}
<p>
Zig allows the address to be taken of a non-byte-aligned field:
</p>
{#code_begin|test#}
const std = @import("std");
const assert = std.debug.assert;
const BitField = packed struct {
a: u3,
b: u3,
c: u2,
};
var foo = BitField{
.a = 1,
.b = 2,
.c = 3,
};
test "pointer to non-byte-aligned field" {
const ptr = &foo.b;
assert(ptr.* == 2);
}
{#code_end#}
<p>
However, the pointer to a non-byte-aligned field has special properties and cannot
be passed when a normal pointer is expected:
</p>
{#code_begin|test_err|expected type#}
const std = @import("std");
const assert = std.debug.assert;
const BitField = packed struct {
a: u3,
b: u3,
c: u2,
};
var bit_field = BitField{
.a = 1,
.b = 2,
.c = 3,
};
test "pointer to non-bit-aligned field" {
assert(bar(&bit_field.b) == 2);
}
fn bar(x: *const u3) u3 {
return x.*;
}
{#code_end#}
<p>
In this case, the function {#syntax#}bar{#endsyntax#} cannot be called becuse the pointer
to the non-byte-aligned field mentions the bit offset, but the function expects a byte-aligned pointer.
</p>
<p>
Pointers to non-byte-aligned fields share the same address as the other fields within their host integer:
</p>
{#code_begin|test#}
const std = @import("std");
const assert = std.debug.assert;
const BitField = packed struct {
a: u3,
b: u3,
c: u2,
};
var bit_field = BitField{
.a = 1,
.b = 2,
.c = 3,
};
test "pointer to non-bit-aligned field" {
assert(@ptrToInt(&bit_field.a) == @ptrToInt(&bit_field.b));
assert(@ptrToInt(&bit_field.a) == @ptrToInt(&bit_field.c));
}
{#code_end#}
<p>
This can be observed with {#link|@bitOffsetOf#} and {#link|byteOffsetOf#}:
</p>
{#code_begin|test#}
const std = @import("std");
const assert = std.debug.assert;
const BitField = packed struct {
a: u3,
b: u3,
c: u2,
};
test "pointer to non-bit-aligned field" {
comptime {
assert(@bitOffsetOf(BitField, "a") == 0);
assert(@bitOffsetOf(BitField, "b") == 3);
assert(@bitOffsetOf(BitField, "c") == 6);
assert(@byteOffsetOf(BitField, "a") == 0);
assert(@byteOffsetOf(BitField, "b") == 0);
assert(@byteOffsetOf(BitField, "c") == 0);
}
}
{#code_end#}
<p>
Packed structs have 1-byte alignment. However if you have an overaligned pointer to a packed struct,
Zig should correctly understand the alignment of fields. However there is
<a href="https://github.com/ziglang/zig/issues/1994">a bug</a>:
</p>
{#code_begin|test_err#}
const S = packed struct {
a: u32,
b: u32,
};
test "overaligned pointer to packed struct" {
var foo: S align(4) = undefined;
const ptr: *align(4) S = &foo;
const ptr_to_b: *u32 = &ptr.b;
}
{#code_end#}
<p>When this bug is fixed, the above test in the documentation will unexpectedly pass, which will
cause the test suite to fail, notifying the bug fixer to update these docs.
</p>
<p>
It's also
<a href="https://github.com/ziglang/zig/issues/1512">planned to be able to set alignment of struct fields</a>.
</p>
<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.
</p>
{#header_close#}
{#header_open|struct Naming#}
<p>Since all structs are anonymous, Zig infers the type name based on a few rules.</p>
@ -2203,8 +2393,8 @@ export fn entry(foo: Foo) void { }
{#header_close#}
{#header_open|packed enum#}
<p>By default, the size of enums is not guaranteed.</p>
<p>{#syntax#}packed enum{#endsyntax#} causes the size of the enum to be the same as the size of the integer tag type
of the enum:</p>
<p>{#syntax#}packed enum{#endsyntax#} causes the size of the enum to be the same as the size of the
integer tag type of the enum:</p>
{#code_begin|test#}
const std = @import("std");
@ -2217,6 +2407,7 @@ test "packed enum" {
std.debug.assert(@sizeOf(Number) == @sizeOf(u8));
}
{#code_end#}
<p>This makes the enum eligible to be in a {#link|packed struct#}.</p>
{#header_close#}
{#see_also|@memberName|@memberCount|@tagName|@sizeOf#}
{#header_close#}
@ -2344,7 +2535,12 @@ test "@tagName" {
Unions with an enum tag are generated as a struct with a tag field and union field. Zig
sorts the order of the tag and union field by the largest alignment.
</p>
{#header_open|packed union#}
<p>A {#syntax#}packed union{#endsyntax#} has well-defined in-memory layout and is eligible
to be in a {#link|packed struct#}.
{#header_close#}
{#header_close#}
{#header_open|blocks#}
<p>
Blocks are used to limit the scope of variable declarations: