Pointer arithmetic
Adding an integer to a pointer moves it by whole elements, not bytes. If p is a *T, then p + 1 is the address of the next T — sizeof(T) bytes further on — whatever T is. This is how a program walks memory that has no length attached: an allocation, a buffer from a foreign function, the elements behind a view's .data.
| Expression | Type | Meaning |
|---|---|---|
p + n | the type of p | n elements after p |
n + p | the type of p | the same as p + n |
p - n | the type of p | n elements before p |
p += n | moves the var pointer p forward n elements | |
p -= n | moves it back n elements | |
p[i] | the pointee, a place | the element i elements after p: *(p + i) |
n may have any integer type. The result keeps the pointer's writability: an offset *var T is still a *var T.
var values: int64[4] = [10, 20, 30, 40];
let first = @values[0];
let third = first + 2; // 16 bytes on: sizeof(int64) is 8
let a = *third; // 30
let b = *(third - 1); // 20
let c = first[3]; // 40
let d = third[-1]; // 20: an index may be negative
first[1] = 25; // values[1] is now 25
Indexing and arithmetic agree — @p[i] is the same address as p + i — and both scale by the pointee's size, including a struct's size with its padding. See Layout.
* binds more tightly than +, so the parentheses in *(p + 2) matter: *p + 2 reads the first element and adds 2 to it. p[2] says the same as *(p + 2) with no parentheses.
Nothing is checked
A pointer carries no length, so neither arithmetic nor p[i] is bounds-checked. An offset past the end of the memory a pointer addresses produces an address that is not one, and reading or writing through it is undefined: the program may crash, or may silently read or overwrite something else. The caller vouches for every offset. When the count is known, build a view, p[..n], whose indexing is checked.
Walking a buffer
Pointers order with <, <=, > and >=, so a loop can walk from a start pointer to an end pointer one past the last element. += steps the cursor; there is no ++ or -- on a pointer:
func Main() -> int {
var values: int64[4] = [10, 20, 30, 40];
let start = @values[0];
let end = start + values.length; // one past the last element: never read
var cursor = start;
var sum: int64 = 0;
while cursor < end {
sum += *cursor;
cursor += 1;
}
PrintLine("{}", sum); // 100
return 0;
}
Compare with <, not <=: the end pointer marks where to stop, and reading it reads past the array.
Pointer subtraction
Subtracting one pointer from another is not defined:
error: operator '-' cannot combine left operand '*var int64' with right operand '*var int64'
To count the elements between two pointers into the same block, convert both to addresses and divide the distance in bytes by the element size:
let count = ((end as uint) - (start as uint)) / sizeof(int64); // 4
*opaque
A *opaque has no element type, so arithmetic on it moves by single bytes: block + 8 is eight bytes on. To step by elements, cast it to a typed pointer first. Indexing an *opaque yields no usable value; cast it before reading or writing.
See also
- Pointers —
*T,*var T,@andnull - Pointer slicing — a checked view built from a pointer and a count
- Layout — the element sizes that arithmetic scales by
- Learn: Pointer arithmetic, Raw memory