Unsurprisingly, Go arrays are laid out contiguously in memory. Then since Go types are statically sized, the address of the nth item is equal to the address of the 0th element plus a byte offset equal to the size of the type of the item.
This can be roughly formalized as (pseudo code):
addr_n = addr_0 + (n * size_of(item))
And in Go code, using unsafe.Add
(since Go 1.17):
func main() {
const a = 1111111111111111111
x := [7]int{a, 1, 33333, 4, 6, 7, 7}
unsafePointer := unsafe.Pointer(&x[0])
for i := range x {
step := unsafe.Sizeof(int(0))
addr_n := unsafe.Add(unsafePointer, int(step)*i)
fmt.Printf("addr: %p, val: %d\n", addr_n, *(*int)(addr_n))
}
}
Which prints:
addr: 0xc000102000, val: 1111111111111111111
addr: 0xc000102008, val: 1
addr: 0xc000102010, val: 33333
addr: 0xc000102018, val: 4
addr: 0xc000102020, val: 6
addr: 0xc000102028, val: 7
addr: 0xc000102030, val: 7
In case it wasn't already crystal clear, the hex number is the memory address. This is how pointers are usually formatted by the fmt
package.
However note that the size of int
in particular is platform-dependent, hence in the snippet above you can't just add 8
. To make it deterministic, you can use unsafe.Sizeof(int(0))
.
Playground: https://go.dev/play/p/4hu8efVed96