Null and Pointer Values
Zig pointers are safety checked to ensure that the address of 0
is not allowed. Clasically, this is called null-ptr
and is often used to denote a pointer that is not currently pointing to a valid address. To denote such a pointer in Zig, we use optional types:
// optional pointer with value
const ptr: ?*u8 = &byte; // take address of byte
// optional pointer that's null
const ptr: ?*u8 = null; // set to null
Note that zero-addresses can be allowed: Documentation - The Zig Programming Language
C-pointers and the Zero Address
Note:
- C-pointers are discouraged. Instead, prefer to use the correct Zig-pointer for your task.
C-style pointers do not have this same restriction and can infact use the value 0
without safety checks:
// classic null-ptr
const ptr: [*c]u8 = @ptrFromInt(0);
These pointer types can still be used as if they were optionals:
const ptr: [*c]u8 = @ptrFromInt(0);
// check if pointer has value of 0
if (ptr == null) ...
// also works with orelse syntax
_ = ptr orelse return null;
Footgun - Optional C-pointer
Consider the following example:
const p1: [*c]u8 = @ptrFromInt(0);
const p2: [*c]u8 = p1;
return p2 == null;
Obviously, we can see that this will in fact return true
because p2
is just a copy of p1
and p1
has the zero-address.
Now consider adding an optional qualification:
const p1: [*c]u8 = @ptrFromInt(0);
const p2: ?[*c]u8 = p1; // is optional now
return p2 == null;
The user may anticipate that p2
should now be null because it’s being assigned to from a pointer with the zero-address. This is not true though.
In the last example, p2
is now an optional pointer - it either holds a pointer value or the value of null
. In this case, the optional is not null but the pointer itself has the value of a null-ptr
.
Dereferencing the value contained by the optional will create a segfault:
p2.?.* // segfault
This can cause sneaky bugs where the user believed they checked for a null
value, but they checked the optional itself, not the value of the pointer that was contained by the optional.
Recommendation
Be careful about adding optional qualifiers to [*c]
type pointers. If you want to see if the [*c]
pointer has the value of 0
, just check by directly comparing it to null as in ptr == null
or use an orelse
.
In short, don’t confuse the optional-pointer’s null value for the address of the pointer itself when dealing with C-pointers.
Only use optional C-pointers ?[*c]
when you intend to convey that there may not be a pointer assigned to that value. Ideally use a concrete normal optional pointer instead (like e.g. ?[*:0]u8
if the data is a zero terminated string where the pointer itself can be null).
C-pointers can be coerced to concrete normal Zig pointer types and optional pointer types, using the more concrete Zig types, helps in avoiding invalid uses. This requires you to understand what the more concrete Zig type is (this is different based on the example) and declare that type in your Zig code, that way Zig can enforce the correct usage. Also read the Language Reference: C-Pointers for more details.
Here is an example where a complex pointer type is described exactly on the Zig side: Null terminated array of strings from C function - #12 by castholm