unsafe.Pointer
Most pointer types are written *T
, meaning “a pointer to a variable
of type T
.” The unsafe.Pointer
type is a special kind of pointer that can
hold the address of any variable. Of course, we can’t indirect through an
unsafe.Pointer
using *p
because we don’t know what type that
expression should have. Like ordinary pointers, unsafe.Pointer
s are
comparable and may be compared with nil
, which is the zero value of
the type.
An ordinary *T
pointer may be converted to an
unsafe.Pointer
, and an unsafe.Pointer
may be converted back
to an ordinary pointer, not necessarily of the same type *T
.
By converting a *float64
pointer to a *uint64
, for
instance, we can inspect the bit pattern of a floating-point variable:
package math func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) } fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
Through the resulting pointer, we can update the bit pattern too.
This is harmless for a floating-point variable since any bit pattern is
legal, but in general, unsafe.Pointer
conversions let us write
arbitrary values to memory and thus subvert the type system.
An unsafe.Pointer
may also be converted to a uintptr
that holds the pointer’s numeric value, letting us perform arithmetic
on addresses.
(Recall from Chapter 3 that a uintptr
is an
unsigned integer wide enough to represent an address.)
This conversion too may be applied in reverse, but again, converting
from a uintptr
to an unsafe.Pointer
may subvert the type
system since not all numbers are valid addresses.
Many unsafe.Pointer
values are thus intermediaries for converting
ordinary pointers to raw numeric addresses and back again. The
example below takes the address of variable x
, adds the offset of
its b
field, converts the resulting address to *int16
, and through
that pointer updates x.b
:
var x struct { a bool b int16 c []int } // equivalent to pb := &x.b pb := (*int16)(unsafe.Pointer( uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b))) *pb = 42 fmt.Println(x.b) // "42"
Although the syntax is cumbersome—perhaps no bad thing since these
features should be used sparingly—do not be tempted to introduce
temporary variables of type uintptr
to break the lines. This
code is incorrect:
// NOTE: subtly incorrect! tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b) pb := (*int16)(unsafe.Pointer(tmp)) *pb = 42
The reason is very subtle.
Some garbage collectors move variables around in memory to
reduce fragmentation or bookkeeping.
Garbage collectors of this kind are known as moving GCs.
When a variable is moved, all pointers that hold the address of the
old location must be updated to point to the new one.
From the perspective of the garbage collector, an unsafe.Pointer
is a
pointer and thus its value must change as the variable moves, but a
uintptr
is just a number so its value must not change. The
incorrect code above hides a pointer
from the garbage collector in
the non-pointer variable tmp
. By the time the second statement
executes, the variable x
could have moved and the number in tmp
would no longer be the address &x.b
. The third statement clobbers
an arbitrary memory location with the value 42.
There are myriad pathological variations on this theme. After this statement has executed:
pT := uintptr(unsafe.Pointer(new(T))) // NOTE: wrong!
there are no pointers that refer to the variable created by new
, so
the garbage collector is entitled to recycle its storage when this statement
completes, after which pT
contains the address where the variable was but
is no longer.
No current Go implementation uses a moving garbage collector (though future implementations might), but this is no reason for complacency: current versions of Go do move some variables around in memory. Recall from Section 5.2 that goroutine stacks grow as needed. When this happens, all variables on the old stack may be relocated to a new, larger stack, so we cannot rely on the numeric value of a variable’s address remaining unchanged throughout its lifetime.
At the time of writing, there is little clear guidance on what Go
programmers may rely upon after an unsafe.Pointer
to uintptr
conversion (see Go issue 7192),
so we strongly recommend
that you assume the bare minimum. Treat all uintptr
values as if
they contain the former address of a variable, and minimize the
number of operations between converting an unsafe.Pointer
to a
uintptr
and using that uintptr
. In our first example above, the
three operations—conversion to a uintptr
, addition of the field
offset, conversion back—all appeared within a single expression.
When calling a library function that returns a uintptr
, such as
those below from the reflect
package, the result should be
immediately converted to an unsafe.Pointer
to ensure that it
continues to point to the same variable.
package reflect func (Value) Pointer() uintptr func (Value) UnsafeAddr() uintptr func (Value) InterfaceData() [2]uintptr // (index 1)