Pointers

Zig has two kinds of pointers:

  • *T - pointer to exactly one item.
    • Supports deref syntax: ptr.*
  • [*]T - 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 be c_void or any other @OpaqueType.

These types are closely related to Arrays and Slices:

  • *[N]T - pointer to N items, same as single-item pointer to array.
    • Supports index syntax: array_ptr[i]
    • Supports slice syntax: array_ptr[start..end]
    • Supports len property: array_ptr.len
  • []T - pointer to runtime-known number of items.
    • Supports index syntax: slice[i]
    • Supports slice syntax: slice[start..end]
    • Supports len property: slice.len

Use &x to obtain a single-item pointer:

test.zig

  1. const assert = @import("std").debug.assert;
  2. test "address of syntax" {
  3. // Get the address of a variable:
  4. const x: i32 = 1234;
  5. const x_ptr = &x;
  6. // Dereference a pointer:
  7. assert(x_ptr.* == 1234);
  8. // When you get the address of a const variable, you get a const pointer to a single item.
  9. assert(@typeOf(x_ptr) == *const i32);
  10. // If you want to mutate the value, you'd need an address of a mutable variable:
  11. var y: i32 = 5678;
  12. const y_ptr = &y;
  13. assert(@typeOf(y_ptr) == *i32);
  14. y_ptr.* += 1;
  15. assert(y_ptr.* == 5679);
  16. }
  17. test "pointer array access" {
  18. // Taking an address of an individual element gives a
  19. // pointer to a single item. This kind of pointer
  20. // does not support pointer arithmetic.
  21. var array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  22. const ptr = &array[2];
  23. assert(@typeOf(ptr) == *u8);
  24. assert(array[2] == 3);
  25. ptr.* += 1;
  26. assert(array[2] == 4);
  27. }
  1. $ zig test test.zig
  2. 1/2 test "address of syntax"...OK
  3. 2/2 test "pointer array access"...OK
  4. All tests passed.

In Zig, we prefer slices over pointers to null-terminated arrays. 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.

test.zig

  1. const assert = @import("std").debug.assert;
  2. test "pointer slicing" {
  3. var array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  4. const slice = array[2..4];
  5. assert(slice.len == 2);
  6. assert(array[3] == 4);
  7. slice[1] += 1;
  8. assert(array[3] == 5);
  9. }
  1. $ zig test test.zig
  2. 1/1 test "pointer slicing"...OK
  3. All tests passed.

Pointers work at compile-time too, as long as the code does not depend on an undefined memory layout:

test.zig

  1. const assert = @import("std").debug.assert;
  2. test "comptime pointers" {
  3. comptime {
  4. var x: i32 = 1;
  5. const ptr = &x;
  6. ptr.* += 1;
  7. x += 1;
  8. assert(ptr.* == 3);
  9. }
  10. }
  1. $ zig test test.zig
  2. 1/1 test "comptime pointers"...OK
  3. All tests passed.

To convert an integer address into a pointer, use @intToPtr. To convert a pointer to an integer, use @ptrToInt:

test.zig

  1. const assert = @import("std").debug.assert;
  2. test "@ptrToInt and @intToPtr" {
  3. const ptr = @intToPtr(*i32, 0xdeadbeef);
  4. const addr = @ptrToInt(ptr);
  5. assert(@typeOf(addr) == usize);
  6. assert(addr == 0xdeadbeef);
  7. }
  1. $ zig test test.zig
  2. 1/1 test "@ptrToInt and @intToPtr"...OK
  3. All tests passed.

Zig is able to preserve memory addresses in comptime code, as long as the pointer is never dereferenced:

test.zig

  1. const assert = @import("std").debug.assert;
  2. test "comptime @intToPtr" {
  3. comptime {
  4. // Zig is able to do this at compile-time, as long as
  5. // ptr is never dereferenced.
  6. const ptr = @intToPtr(*i32, 0xdeadbeef);
  7. const addr = @ptrToInt(ptr);
  8. assert(@typeOf(addr) == usize);
  9. assert(addr == 0xdeadbeef);
  10. }
  11. }
  1. $ zig test test.zig
  2. 1/1 test "comptime @intToPtr"...OK
  3. All tests passed.

See also:

volatile

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. In the following code, loads and stores with mmio_ptr are guaranteed to all happen and in the same order as in source code:

test.zig

  1. const assert = @import("std").debug.assert;
  2. test "volatile" {
  3. const mmio_ptr = @intToPtr(*volatile u8, 0x12345678);
  4. assert(@typeOf(mmio_ptr) == *volatile u8);
  5. }
  1. $ zig test test.zig
  2. 1/1 test "volatile"...OK
  3. All tests passed.

Note that volatile is unrelated to concurrency and Atomics. 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.

test.zig

  1. const assert = @import("std").debug.assert;
  2. test "pointer casting" {
  3. const bytes align(@alignOf(u32)) = [_]u8{ 0x12, 0x12, 0x12, 0x12 };
  4. const u32_ptr = @ptrCast(*const u32, &bytes);
  5. assert(u32_ptr.* == 0x12121212);
  6. // Even this example is contrived - there are better ways to do the above than
  7. // pointer casting. For example, using a slice narrowing cast:
  8. const u32_value = @bytesToSlice(u32, bytes[0..])[0];
  9. assert(u32_value == 0x12121212);
  10. // And even another way, the most straightforward way to do it:
  11. assert(@bitCast(u32, bytes) == 0x12121212);
  12. }
  13. test "pointer child type" {
  14. // pointer types have a `child` field which tells you the type they point to.
  15. assert((*u32).Child == u32);
  16. }
  1. $ zig test test.zig
  2. 1/2 test "pointer casting"...OK
  3. 2/2 test "pointer child type"...OK
  4. All tests passed.

Alignment

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 @alignOf 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.

In Zig, a pointer type has an alignment value. If the value is equal to the alignment of the underlying type, it can be omitted from the type:

test.zig

  1. const assert = @import("std").debug.assert;
  2. const builtin = @import("builtin");
  3. test "variable alignment" {
  4. var x: i32 = 1234;
  5. const align_of_i32 = @alignOf(@typeOf(x));
  6. assert(@typeOf(&x) == *i32);
  7. assert(*i32 == *align(align_of_i32) i32);
  8. if (builtin.arch == builtin.Arch.x86_64) {
  9. assert((*i32).alignment == 4);
  10. }
  11. }
  1. $ zig test test.zig
  2. 1/1 test "variable alignment"...OK
  3. All tests passed.

In the same way that a *i32 can be implicitly cast 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:

test.zig

  1. const assert = @import("std").debug.assert;
  2. var foo: u8 align(4) = 100;
  3. test "global variable alignment" {
  4. assert(@typeOf(&foo).alignment == 4);
  5. assert(@typeOf(&foo) == *align(4) u8);
  6. const slice = (*[1]u8)(&foo)[0..];
  7. assert(@typeOf(slice) == []align(4) u8);
  8. }
  9. fn derp() align(@sizeOf(usize) * 2) i32 { return 1234; }
  10. fn noop1() align(1) void {}
  11. fn noop4() align(4) void {}
  12. test "function alignment" {
  13. assert(derp() == 1234);
  14. assert(@typeOf(noop1) == fn() align(1) void);
  15. assert(@typeOf(noop4) == fn() align(4) void);
  16. noop1();
  17. noop4();
  18. }
  1. $ zig test test.zig
  2. 1/2 test "global variable alignment"...OK
  3. 2/2 test "function alignment"...OK
  4. All 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 @alignCast to change the pointer into a more aligned pointer. This is a no-op at runtime, but inserts a safety check:

test.zig

  1. const assert = @import("std").debug.assert;
  2. test "pointer alignment safety" {
  3. var array align(4) = [_]u32{ 0x11111111, 0x11111111 };
  4. const bytes = @sliceToBytes(array[0..]);
  5. assert(foo(bytes) == 0x11111111);
  6. }
  7. fn foo(bytes: []u8) u32 {
  8. const slice4 = bytes[1..5];
  9. const int_slice = @bytesToSlice(u32, @alignCast(4, slice4));
  10. return int_slice[0];
  11. }
  1. $ zig test test.zig
  2. 1/1 test "pointer alignment safety"...incorrect alignment
  3. /home/andy/dev/zig/docgen_tmp/test.zig:10:56: 0x2057ae in foo (test)
  4. const int_slice = @bytesToSlice(u32, @alignCast(4, slice4));
  5. ^
  6. /home/andy/dev/zig/docgen_tmp/test.zig:6:15: 0x2055b0 in test "pointer alignment safety" (test)
  7. assert(foo(bytes) == 0x11111111);
  8. ^
  9. /home/andy/dev/zig/lib/std/special/test_runner.zig:13:25: 0x2284e1 in std.special.main (test)
  10. if (test_fn.func()) |_| {
  11. ^
  12. /home/andy/dev/zig/lib/std/special/start.zig:204:37: 0x227355 in std.special.posixCallMainAndExit (test)
  13. const result = root.main() catch |err| {
  14. ^
  15. /home/andy/dev/zig/lib/std/special/start.zig:102:5: 0x2271cf in std.special._start (test)
  16. @noInlineCall(posixCallMainAndExit);
  17. ^
  18. Tests failed. Use the following command to reproduce the failure:
  19. /home/andy/dev/zig/docgen_tmp/test

allowzero

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 Optional Pointers instead. In this code example, if the pointer did not have the allowzero attribute, this would be a Pointer Cast Invalid Null panic:

allowzero.zig

  1. const std = @import("std");
  2. const assert = std.debug.assert;
  3. test "allowzero" {
  4. var zero: usize = 0;
  5. var ptr = @intToPtr(*allowzero i32, zero);
  6. assert(@ptrToInt(ptr) == 0);
  7. }
  1. $ zig test allowzero.zig
  2. 1/1 test "allowzero"...OK
  3. All tests passed.