Pointers
- - single-item pointer to exactly one item.
- Supports deref syntax:
ptr.*
- Supports deref syntax:
[*]T
- many-item pointer to unknown number of items.- Supports index syntax:
ptr[i]
- Supports slice syntax:
ptr[start..end]
- Supports pointer arithmetic:
ptr + x
,ptr - x
T
must have a known size, which means that it cannot bec_void
or any other .
- Supports index syntax:
These types are closely related to Arrays and :
*[N]T
- pointer to N items, same as single-item pointer to an array.- Supports index syntax:
array_ptr[i]
- Supports slice syntax:
array_ptr[start..end]
- Supports len property:
array_ptr.len
- Supports index syntax:
[]T
- pointer to runtime-known number of items.- Supports index syntax:
slice[i]
- Supports slice syntax:
slice[start..end]
- Supports len property:
slice.len
- Supports index syntax:
Use &x
to obtain a single-item pointer:
single_item_pointer_test.zig
Shell
$ zig test single_item_pointer_test.zig
1/2 test "address of syntax"... OK
2/2 test "pointer array access"... OK
All 2 tests passed.
In Zig, we generally prefer Slices rather than . You can turn an array or pointer into a slice using slice syntax.
Slices have bounds checking and are therefore protected against this kind of undefined behavior. This is one reason we prefer slices to pointers.
slice_bounds.zig
const expect = @import("std").testing.expect;
test "pointer slicing" {
var array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
const slice = array[2..4];
try expect(slice.len == 2);
try expect(array[3] == 4);
slice[1] += 1;
try expect(array[3] == 5);
}
Shell
$ zig test slice_bounds.zig
1/1 test "pointer slicing"... OK
All 1 tests passed.
Pointers work at compile-time too, as long as the code does not depend on an undefined memory layout:
comptime_pointers.zig
const expect = @import("std").testing.expect;
test "comptime pointers" {
comptime {
var x: i32 = 1;
const ptr = &x;
ptr.* += 1;
x += 1;
try expect(ptr.* == 3);
}
}
Shell
$ zig test comptime_pointers.zig
1/1 test "comptime pointers"... OK
All 1 tests passed.
To convert an integer address into a pointer, use @intToPtr
. To convert a pointer to an integer, use @ptrToInt
:
const expect = @import("std").testing.expect;
test "@ptrToInt and @intToPtr" {
const ptr = @intToPtr(*i32, 0xdeadbee0);
const addr = @ptrToInt(ptr);
try expect(@TypeOf(addr) == usize);
try expect(addr == 0xdeadbee0);
}
Shell
$ zig test integer_pointer_conversion.zig
1/1 test "@ptrToInt and @intToPtr"... OK
All 1 tests passed.
Zig is able to preserve memory addresses in comptime code, as long as the pointer is never dereferenced:
comptime_pointer_conversion.zig
Shell
$ zig test comptime_pointer_conversion.zig
All 1 tests passed.
See also:
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 . In the following code, loads and stores with mmio_ptr
are guaranteed to all happen and in the same order as in source code:
volatile.zig
const expect = @import("std").testing.expect;
test "volatile" {
const mmio_ptr = @intToPtr(*volatile u8, 0x12345678);
try expect(@TypeOf(mmio_ptr) == *volatile u8);
}
Shell
$ zig test volatile.zig
1/1 test "volatile"... OK
All 1 tests passed.
Note that volatile
is unrelated to concurrency and . If you see code that is using volatile
for something other than Memory Mapped Input/Output, it is probably a bug.
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.
pointer_casting.zig
const std = @import("std");
const expect = std.testing.expect;
test "pointer casting" {
const bytes align(@alignOf(u32)) = [_]u8{ 0x12, 0x12, 0x12, 0x12 };
const u32_ptr = @ptrCast(*const u32, &bytes);
try expect(u32_ptr.* == 0x12121212);
// Even this example is contrived - there are better ways to do the above than
// pointer casting. For example, using a slice narrowing cast:
const u32_value = std.mem.bytesAsSlice(u32, bytes[0..])[0];
try expect(u32_value == 0x12121212);
// And even another way, the most straightforward way to do it:
try expect(@bitCast(u32, bytes) == 0x12121212);
}
test "pointer child type" {
// pointer types have a `child` field which tells you the type they point to.
try expect(@typeInfo(*u32).Pointer.child == u32);
}
Shell
$ zig test pointer_casting.zig
1/2 test "pointer casting"... OK
2/2 test "pointer child type"... OK
All 2 tests passed.
Each type has an alignment - a number of bytes such that, when a value of the type is loaded from or stored to memory, the memory address must be evenly divisible by this number. You can use to find out this value for any type.
Alignment depends on the CPU architecture, but is always a power of two, and less than 1 << 29
.
variable_alignment.zig
const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;
test "variable alignment" {
var x: i32 = 1234;
const align_of_i32 = @alignOf(@TypeOf(x));
try expect(@TypeOf(&x) == *i32);
try expect(*i32 == *align(align_of_i32) i32);
if (builtin.target.cpu.arch == .x86_64) {
try expect(@typeInfo(*i32).Pointer.alignment == 4);
}
}
Shell
$ zig test variable_alignment.zig
1/1 test "variable alignment"... OK
All 1 tests passed.
In the same way that a *i32
can be coerced to a *const i32
, a pointer with a larger alignment can be implicitly cast to a pointer with a smaller alignment, but not vice versa.
You can specify alignment on variables and functions. If you do this, then pointers to them get the specified alignment:
variable_func_alignment.zig
Shell
$ zig test variable_func_alignment.zig
1/2 test "global variable alignment"... OK
2/2 test "function alignment"... OK
All 2 tests passed.
If you have a pointer or a slice that has a small alignment, but you know that it actually has a bigger alignment, use to change the pointer into a more aligned pointer. This is a no-op at runtime, but inserts a safety check:
test.zig
const std = @import("std");
test "pointer alignment safety" {
try std.testing.expect(foo(bytes) == 0x11111111);
}
fn foo(bytes: []u8) u32 {
const slice4 = bytes[1..5];
const int_slice = std.mem.bytesAsSlice(u32, @alignCast(4, slice4));
return int_slice[0];
}
Shell
$ zig test test.zig
1/1 test "pointer alignment safety"... thread 792246 panic: incorrect alignment
/home/andy/Downloads/zig/docgen_tmp/test.zig:10:63: 0x2080ec in foo (test)
const int_slice = std.mem.bytesAsSlice(u32, @alignCast(4, slice4));
^
/home/andy/Downloads/zig/docgen_tmp/test.zig:6:31: 0x207a69 in test "pointer alignment safety" (test)
try std.testing.expect(foo(bytes) == 0x11111111);
^
/home/andy/Downloads/zig/lib/std/special/test_runner.zig:80:28: 0x22f6d3 in std.special.main (test)
} else test_fn.func();
^
/home/andy/Downloads/zig/lib/std/start.zig:543:22: 0x2280cc in std.start.callMain (test)
root.main();
^
/home/andy/Downloads/zig/lib/std/start.zig:495:12: 0x20953e in std.start.callMainWithArgs (test)
return @call(.{ .modifier = .always_inline }, callMain, .{});
^
/home/andy/Downloads/zig/lib/std/start.zig:409:17: 0x208546 in std.start.posixCallMainAndExit (test)
std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));
^
/home/andy/Downloads/zig/lib/std/start.zig:322:5: 0x208352 in std.start._start (test)
@call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
^
error: the following test command crashed:
docgen_tmp/zig-cache/o/8e4964e05ddaf866e77360a551b3c05b/test /home/andy/Downloads/zig/build-release/zig
This pointer attribute allows a pointer to have address zero. This is only ever needed on the freestanding OS target, where the address zero is mappable. If you want to represent null pointers, use instead. Optional Pointers with allowzero
are not the same size as pointers. In this code example, if the pointer did not have the allowzero
attribute, this would be a panic:
allowzero.zig
const std = @import("std");
const expect = std.testing.expect;
test "allowzero" {
var zero: usize = 0;
var ptr = @intToPtr(*allowzero i32, zero);
try expect(@ptrToInt(ptr) == 0);
}
Shell
$ zig test allowzero.zig
1/1 test "allowzero"... OK
All 1 tests passed.
The syntax [*:x]T
describes a pointer that has a length determined by a sentinel value. This provides protection against buffer overflow and overreads.
test.zig
const std = @import("std");
// This is also available as `std.c.printf`.
pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;
pub fn main() anyerror!void {
_ = printf("Hello, world!\n"); // OK
const msg = "Hello, world!\n";
const non_null_terminated_msg: [msg.len]u8 = msg.*;
_ = printf(&non_null_terminated_msg);
}
$ zig build-exe test.zig -lc
./docgen_tmp/test.zig:11:17: error: expected type '[*:0]const u8', found '*const [14]u8'
_ = printf(&non_null_terminated_msg);
^
./docgen_tmp/test.zig:11:17: note: destination pointer requires a terminating '0' sentinel
_ = printf(&non_null_terminated_msg);
^
See also: