improve pointer documentation

closes #1630
master
Andrew Kelley 2018-10-04 22:51:36 -04:00
parent d07413f9b7
commit 8d6601d7ce
No known key found for this signature in database
GPG Key ID: 4E7CD66038A4D47C
1 changed files with 132 additions and 65 deletions

View File

@ -1504,6 +1504,45 @@ test "array initialization with function calls" {
{#see_also|for|Slices#} {#see_also|for|Slices#}
{#header_close#} {#header_close#}
{#header_open|Pointers#} {#header_open|Pointers#}
<p>
Zig has two kinds of pointers:
</p>
<ul>
<li>{#syntax#}*T{#endsyntax#} - pointer to exactly one item.
<ul>
<li>Supports deref syntax: {#syntax#}ptr.*{#endsyntax#}</li>
</ul>
</li>
<li>{#syntax#}[*]T{#endsyntax#} - pointer to unknown number of items.
<ul>
<li>Supports index syntax: {#syntax#}ptr[i]{#endsyntax#}</li>
<li>Supports slice syntax: {#syntax#}ptr[start..end]{#endsyntax#}</li>
<li>Supports pointer arithmetic: {#syntax#}ptr + x{#endsyntax#}, {#syntax#}ptr - x{#endsyntax#}</li>
<li>{#syntax#}T{#endsyntax#} must have a known size, which means that it cannot be
{#syntax#}c_void{#endsyntax#} or any other {#link|@OpaqueType#}.</li>
</ul>
</li>
</ul>
<p>These types are closely related to {#link|Arrays#} and {#link|Slices#}:</p>
<ul>
<li>{#syntax#}*[N]T{#endsyntax#} - pointer to N items, same as single-item pointer to array.
<ul>
<li>Supports index syntax: {#syntax#}array_ptr[i]{#endsyntax#}</li>
<li>Supports slice syntax: {#syntax#}array_ptr[start..end]{#endsyntax#}</li>
<li>Supports len property: {#syntax#}array_ptr.len{#endsyntax#}</li>
</ul>
</li>
</ul>
<ul>
<li>{#syntax#}[]T{#endsyntax#} - pointer to runtime-known number of items.
<ul>
<li>Supports index syntax: {#syntax#}slice[i]{#endsyntax#}</li>
<li>Supports slice syntax: {#syntax#}slice[start..end]{#endsyntax#}</li>
<li>Supports len property: {#syntax#}slice.len{#endsyntax#}</li>
</ul>
</li>
</ul>
<p>Use {#syntax#}&x{#endsyntax#} to obtain a single-item pointer:</p>
{#code_begin|test#} {#code_begin|test#}
const assert = @import("std").debug.assert; const assert = @import("std").debug.assert;
@ -1515,7 +1554,7 @@ test "address of syntax" {
// Deference a pointer: // Deference a pointer:
assert(x_ptr.* == 1234); assert(x_ptr.* == 1234);
// When you get the address of a const variable, you get a const pointer. // When you get the address of a const variable, you get a const pointer to a single item.
assert(@typeOf(x_ptr) == *const i32); assert(@typeOf(x_ptr) == *const i32);
// If you want to mutate the value, you'd need an address of a mutable variable: // If you want to mutate the value, you'd need an address of a mutable variable:
@ -1538,43 +1577,62 @@ test "pointer array access" {
ptr.* += 1; ptr.* += 1;
assert(array[2] == 4); assert(array[2] == 4);
} }
{#code_end#}
<p>
In Zig, we prefer slices over pointers to null-terminated arrays.
You can turn an array or pointer into a slice using slice syntax.
</p>
<p>
Slices have bounds checking and are therefore protected
against this kind of undefined behavior. This is one reason
we prefer slices to pointers.
</p>
{#code_begin|test#}
const assert = @import("std").debug.assert;
test "pointer slicing" { test "pointer slicing" {
// In Zig, we prefer slices over pointers to null-terminated arrays.
// You can turn an array into a slice using slice syntax:
var array = []u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; var array = []u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
const slice = array[2..4]; const slice = array[2..4];
assert(slice.len == 2); assert(slice.len == 2);
// Slices have bounds checking and are therefore protected
// against this kind of undefined behavior. This is one reason
// we prefer slices to pointers.
assert(array[3] == 4); assert(array[3] == 4);
slice[1] += 1; slice[1] += 1;
assert(array[3] == 5); assert(array[3] == 5);
} }
{#code_end#}
<p>Pointers work at compile-time too, as long as the code does not depend on
an undefined memory layout:</p>
{#code_begin|test#}
const assert = @import("std").debug.assert;
test "comptime pointers" {
comptime { comptime {
// Pointers work at compile-time too, as long as you don't use
// @ptrCast.
var x: i32 = 1; var x: i32 = 1;
const ptr = &x; const ptr = &x;
ptr.* += 1; ptr.* += 1;
x += 1; x += 1;
assert(ptr.* == 3); assert(ptr.* == 3);
} }
}
{#code_end#}
<p>To convert an integer address into a pointer, use {#syntax#}@intToPtr{#endsyntax#}.
To convert a pointer to an integer, use {#syntax#}@ptrToInt{#endsyntax#}:</p>
{#code_begin|test#}
const assert = @import("std").debug.assert;
test "@ptrToInt and @intToPtr" { test "@ptrToInt and @intToPtr" {
// To convert an integer address into a pointer, use @intToPtr:
const ptr = @intToPtr(*i32, 0xdeadbeef); const ptr = @intToPtr(*i32, 0xdeadbeef);
// To convert a pointer to an integer, use @ptrToInt:
const addr = @ptrToInt(ptr); const addr = @ptrToInt(ptr);
assert(@typeOf(addr) == usize); assert(@typeOf(addr) == usize);
assert(addr == 0xdeadbeef); assert(addr == 0xdeadbeef);
} }
{#code_end#}
<p>Zig is able to preserve memory addresses in comptime code, as long as
the pointer is never dereferenced:</p>
{#code_begin|test#}
const assert = @import("std").debug.assert;
test "comptime @intToPtr" {
comptime { comptime {
// Zig is able to do this at compile-time, as long as // Zig is able to do this at compile-time, as long as
// ptr is never dereferenced. // ptr is never dereferenced.
@ -1583,37 +1641,37 @@ comptime {
assert(@typeOf(addr) == usize); assert(@typeOf(addr) == usize);
assert(addr == 0xdeadbeef); assert(addr == 0xdeadbeef);
} }
}
{#code_end#}
{#see_also|Optional Pointers#}
{#header_open|volatile#}
<p>Loads and stores are assumed to not have side effects. If a given load or store
should have side effects, such as Memory Mapped Input/Output (MMIO), use {#syntax#}volatile{#endsyntax#}.
In the following code, loads and stores with {#syntax#}mmio_ptr{#endsyntax#} are guaranteed to all happen
and in the same order as in source code:</p>
{#code_begin|test#}
const assert = @import("std").debug.assert;
test "volatile" { test "volatile" {
// In Zig, loads and stores are assumed to not have side effects.
// If a given load or store should have side effects, such as
// Memory Mapped Input/Output (MMIO), use `volatile`:
const mmio_ptr = @intToPtr(*volatile u8, 0x12345678); const mmio_ptr = @intToPtr(*volatile u8, 0x12345678);
// Now loads and stores with mmio_ptr are guaranteed to all happen
// and in the same order as in source code.
assert(@typeOf(mmio_ptr) == *volatile u8); assert(@typeOf(mmio_ptr) == *volatile u8);
} }
{#code_end#}
test "optional pointers" { <p>
// Pointers cannot be null. If you want a null pointer, use the optional Note that {#syntax#}volatile{#endsyntax#} is unrelated to concurrency and {#link|Atomics#}.
// prefix `?` to make the pointer type optional. If you see code that is using {#syntax#}volatile{#endsyntax#} for something other than Memory Mapped
var ptr: ?*i32 = null; Input/Output, it is probably a bug.
</p>
var x: i32 = 1; {#header_close#}
ptr = &x; <p>
To convert one pointer type to another, use {#link|@ptrCast#}. This is an unsafe
assert(ptr.?.* == 1); operation that Zig cannot protect you against. Use {#syntax#}@ptrCast{#endsyntax#} only when other
conversions are not possible.
// Optional pointers are the same size as normal pointers, because pointer </p>
// value 0 is used as the null value. {#code_begin|test#}
assert(@sizeOf(?*i32) == @sizeOf(*i32)); const assert = @import("std").debug.assert;
}
test "pointer casting" { test "pointer casting" {
// To convert one pointer type to another, use @ptrCast. This is an unsafe
// operation that Zig cannot protect you against. Use @ptrCast only when other
// conversions are not possible.
const bytes align(@alignOf(u32)) = []u8{ 0x12, 0x12, 0x12, 0x12 }; const bytes align(@alignOf(u32)) = []u8{ 0x12, 0x12, 0x12, 0x12 };
const u32_ptr = @ptrCast(*const u32, &bytes); const u32_ptr = @ptrCast(*const u32, &bytes);
assert(u32_ptr.* == 0x12121212); assert(u32_ptr.* == 0x12121212);
@ -1714,19 +1772,6 @@ fn foo(bytes: []u8) u32 {
} }
{#code_end#} {#code_end#}
{#header_close#} {#header_close#}
{#header_open|Type Based Alias Analysis#}
<p>Zig uses Type Based Alias Analysis (also known as Strict Aliasing) to
perform some optimizations. This means that pointers of different types must
not alias the same memory, with the exception of {#syntax#}u8{#endsyntax#}. Pointers to
{#syntax#}u8{#endsyntax#} can alias any memory.
</p>
<p>As an example, this code produces undefined behavior:</p>
<pre>{#syntax#}@ptrCast(*u32, f32(12.34)).*{#endsyntax#}</pre>
<p>Instead, use {#link|@bitCast#}:
<pre>{#syntax#}@bitCast(u32, f32(12.34)){#endsyntax#}</pre>
<p>As an added benefit, the {#syntax#}@bitCast{#endsyntax#} version works at compile-time.</p>
{#see_also|Slices|Memory#}
{#header_close#}
{#header_close#} {#header_close#}
{#header_open|Slices#} {#header_open|Slices#}
{#code_begin|test_safety|index out of bounds#} {#code_begin|test_safety|index out of bounds#}
@ -3816,6 +3861,28 @@ test "optional type" {
</p> </p>
{#code_begin|syntax#} {#code_begin|syntax#}
const optional_value: ?i32 = null; const optional_value: ?i32 = null;
{#code_end#}
{#header_close#}
{#header_open|Optional Pointers#}
<p>An optional pointer is guaranteed to be the same size as a pointer. The {#syntax#}null{#endsyntax#} of
the optional is guaranteed to be address 0.</p>
{#code_begin|test#}
const assert = @import("std").debug.assert;
test "optional pointers" {
// Pointers cannot be null. If you want a null pointer, use the optional
// prefix `?` to make the pointer type optional.
var ptr: ?*i32 = null;
var x: i32 = 1;
ptr = &x;
assert(ptr.?.* == 1);
// Optional pointers are the same size as normal pointers, because pointer
// value 0 is used as the null value.
assert(@sizeOf(?*i32) == @sizeOf(*i32));
}
{#code_end#} {#code_end#}
{#header_close#} {#header_close#}
{#header_close#} {#header_close#}