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{ body{
background-color:#111; background-color:#111;
color: #bbb; color: #bbb;
font-family: system-ui, font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif;
/* 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;
} }
a { a {
color: #88f; color: #88f;
@ -263,7 +257,7 @@ pub fn main() void {
true and false, true and false,
true or false, true or false,
!true); !true);
// optional // optional
var optional_value: ?[]const u8 = null; var optional_value: ?[]const u8 = null;
assert(optional_value == null); assert(optional_value == null);
@ -282,7 +276,7 @@ pub fn main() void {
warn("\nerror union 1\ntype: {}\nvalue: {}\n", warn("\nerror union 1\ntype: {}\nvalue: {}\n",
@typeName(@typeOf(number_or_error)), number_or_error); @typeName(@typeOf(number_or_error)), number_or_error);
number_or_error = 1234; number_or_error = 1234;
warn("\nerror union 2\ntype: {}\nvalue: {}\n", warn("\nerror union 2\ntype: {}\nvalue: {}\n",
@ -707,15 +701,21 @@ fn divide(a: i32, b: i32) i32 {
{#code_end#} {#code_end#}
<p> <p>
In this function, values {#syntax#}a{#endsyntax#} and {#syntax#}b{#endsyntax#} are known only at runtime, 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 and thus this division operation is vulnerable to both {#link|Integer Overflow#} and
division by zero. {#link|Division by Zero#}.
</p> </p>
<p> <p>
Operators such as {#syntax#}+{#endsyntax#} and {#syntax#}-{#endsyntax#} cause undefined behavior on Operators such as {#syntax#}+{#endsyntax#} and {#syntax#}-{#endsyntax#} cause undefined behavior on
integer overflow. Also available are operations such as {#syntax#}+%{#endsyntax#} and integer overflow. Also available are operations such as {#syntax#}+%{#endsyntax#} and
{#syntax#}-%{#endsyntax#} which are defined to have wrapping arithmetic on all targets. {#syntax#}-%{#endsyntax#} which are defined to have wrapping arithmetic on all targets.
</p> </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_close#} {#header_close#}
{#header_open|Floats#} {#header_open|Floats#}
@ -1652,7 +1652,7 @@ test "pointer slicing" {
assert(array[3] == 5); assert(array[3] == 5);
} }
{#code_end#} {#code_end#}
<p>Pointers work at compile-time too, as long as the code does not depend on <p>Pointers work at compile-time too, as long as the code does not depend on
an undefined memory layout:</p> an undefined memory layout:</p>
{#code_begin|test#} {#code_begin|test#}
const assert = @import("std").debug.assert; const assert = @import("std").debug.assert;
@ -2047,13 +2047,203 @@ test "linked list" {
} }
{#code_end#} {#code_end#}
{#header_open|packed struct#} {#header_open|packed struct#}
<p>{#syntax#}packed{#endsyntax#} structs have guaranteed in-memory layout.</p> <p>
<p>TODO bit fields</p> Unlike normal structs, {#syntax#}packed{#endsyntax#} structs have guaranteed in-memory layout:
<p>TODO alignment</p> </p>
<p>TODO endianness</p> <ul>
<p>TODO @bitOffsetOf and @byteOffsetOf</p> <li>Fields remain in the order declared.</li>
<p>TODO mention how volatile loads and stores of bit packed fields could be more efficient when <li>There is no padding between fields.</li>
done by hand instead of with packed struct</p> <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_close#}
{#header_open|struct Naming#} {#header_open|struct Naming#}
<p>Since all structs are anonymous, Zig infers the type name based on a few rules.</p> <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_close#}
{#header_open|packed enum#} {#header_open|packed enum#}
<p>By default, the size of enums is not guaranteed.</p> <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 <p>{#syntax#}packed enum{#endsyntax#} causes the size of the enum to be the same as the size of the
of the enum:</p> integer tag type of the enum:</p>
{#code_begin|test#} {#code_begin|test#}
const std = @import("std"); const std = @import("std");
@ -2217,6 +2407,7 @@ test "packed enum" {
std.debug.assert(@sizeOf(Number) == @sizeOf(u8)); std.debug.assert(@sizeOf(Number) == @sizeOf(u8));
} }
{#code_end#} {#code_end#}
<p>This makes the enum eligible to be in a {#link|packed struct#}.</p>
{#header_close#} {#header_close#}
{#see_also|@memberName|@memberCount|@tagName|@sizeOf#} {#see_also|@memberName|@memberCount|@tagName|@sizeOf#}
{#header_close#} {#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 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. sorts the order of the tag and union field by the largest alignment.
</p> </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_close#}
{#header_open|blocks#} {#header_open|blocks#}
<p> <p>
Blocks are used to limit the scope of variable declarations: Blocks are used to limit the scope of variable declarations:
@ -3771,7 +3967,7 @@ fn bang2() void {
Here, the stack trace does not explain how the control Here, the stack trace does not explain how the control
flow in {#syntax#}bar{#endsyntax#} got to the {#syntax#}hello(){#endsyntax#} call. flow in {#syntax#}bar{#endsyntax#} got to the {#syntax#}hello(){#endsyntax#} call.
One would have to open a debugger or further instrument the application One would have to open a debugger or further instrument the application
in order to find out. The error return trace, on the other hand, in order to find out. The error return trace, on the other hand,
shows exactly how the error bubbled up. shows exactly how the error bubbled up.
</p> </p>
<p> <p>
@ -3963,7 +4159,7 @@ test "optional type" {
cast it to a different type: cast it to a different type:
</p> </p>
{#code_begin|syntax#} {#code_begin|syntax#}
const optional_value: ?i32 = null; const optional_value: ?i32 = null;
{#code_end#} {#code_end#}
{#header_close#} {#header_close#}
{#header_open|Optional Pointers#} {#header_open|Optional Pointers#}
@ -5141,7 +5337,7 @@ async fn testResumeFromSuspend(my_result: *i32) void {
<p> <p>
{#syntax#}await{#endsyntax#} is valid only in an {#syntax#}async{#endsyntax#} function, and it takes {#syntax#}await{#endsyntax#} is valid only in an {#syntax#}async{#endsyntax#} function, and it takes
as an operand a promise handle. as an operand a promise handle.
If the async function associated with the promise handle has already returned, If the async function associated with the promise handle has already returned,
then {#syntax#}await{#endsyntax#} destroys the target async function, and gives the return value. then {#syntax#}await{#endsyntax#} destroys the target async function, and gives the return value.
Otherwise, {#syntax#}await{#endsyntax#} suspends the current async function, registering its Otherwise, {#syntax#}await{#endsyntax#} suspends the current async function, registering its
promise handle with the target coroutine. It becomes the target coroutine's responsibility promise handle with the target coroutine. It becomes the target coroutine's responsibility
@ -5225,7 +5421,7 @@ fn seq(c: u8) void {
</li> </li>
</ul> </ul>
{#header_close#} {#header_close#}
{#header_close#} {#header_close#}
{#header_open|Builtin Functions#} {#header_open|Builtin Functions#}
<p> <p>
@ -5580,13 +5776,13 @@ const warn = @import("std").debug.warn;
const num1 = blk: { const num1 = blk: {
var val1: i32 = 99; var val1: i32 = 99;
@compileLog("comptime val1 = ", val1); @compileLog("comptime val1 = ", val1);
val1 = val1 + 1; val1 = val1 + 1;
break :blk val1; break :blk val1;
}; };
test "main" { test "main" {
@compileLog("comptime in main"); @compileLog("comptime in main");
warn("Runtime in main, num1 = {}.\n", num1); warn("Runtime in main, num1 = {}.\n", num1);
} }
@ -5596,10 +5792,10 @@ test "main" {
will ouput: will ouput:
</p> </p>
<p> <p>
If all {#syntax#}@compileLog{#endsyntax#} calls are removed or If all {#syntax#}@compileLog{#endsyntax#} calls are removed or
not encountered by analysis, the not encountered by analysis, the
program compiles successfully and the generated executable prints: program compiles successfully and the generated executable prints:
</p> </p>
{#code_begin|test#} {#code_begin|test#}
const warn = @import("std").debug.warn; const warn = @import("std").debug.warn;
@ -6425,7 +6621,7 @@ fn List(comptime T: type) type {
<p> <p>
When {#syntax#}@This(){#endsyntax#} is used at global scope, it returns a reference to the When {#syntax#}@This(){#endsyntax#} is used at global scope, it returns a reference to the
current import. There is a proposal to remove the import type and use an empty struct current import. There is a proposal to remove the import type and use an empty struct
type instead. See type instead. See
<a href="https://github.com/ziglang/zig/issues/1047">#1047</a> for details. <a href="https://github.com/ziglang/zig/issues/1047">#1047</a> for details.
</p> </p>
{#header_close#} {#header_close#}
@ -7560,7 +7756,7 @@ const c = @cImport({
{#link|Undefined Behavior#} occurs if the address is 0. {#link|Undefined Behavior#} occurs if the address is 0.
</li> </li>
<li>Allows address 0. On non-freestanding targets, dereferencing address 0 is safety-checked <li>Allows address 0. On non-freestanding targets, dereferencing address 0 is safety-checked
{#link|Undefined Behavior#}. Optional C pointers introduce another bit to keep track of {#link|Undefined Behavior#}. Optional C pointers introduce another bit to keep track of
null, just like {#syntax#}?usize{#endsyntax#}. Note that creating an optional C pointer null, just like {#syntax#}?usize{#endsyntax#}. Note that creating an optional C pointer
is unnecessary as one can use normal {#link|Optional Pointers#}. is unnecessary as one can use normal {#link|Optional Pointers#}.
</li> </li>