Converting a Go pointer to an integer doesn't quite do what it looks like
Over on r/golang, an interesting question was asked:
[Is it] possible to parse a struct or interface to get its pointer address as an integer? [...]
The practical answer today is yes, as noted in the answers to the
question. You can convert any Go pointer to uintptr
by going through
unsafe.Pointer()
,
and then convert the uintptr
into some more conventional integer
type if you want. If you're going to convert to another integer
type, you should probably use uint64
for safety, since that should
hold any uintptr
value on any current Go platform.
However, the theoretical answer is no, in that this conversion
doesn't quite get you what you might think it does. What this
conversion really gives you is the address that the addressable
value had at the moment the conversion to
uintptr
was done. Go very carefully does not guarantee that this
past address is the same as the current address, although it always
will be today.
(I'm assuming here that there are other references to the addressable value that keep it from being garbage collected.)
Go's current garbage collector is a non-compacting garbage collector, where once things are allocated somewhere in memory, they never move for as long as they're alive. Since a non-compacting garbage collector has stable memory addresses for things, converting an address to an integer gives you something that is always the integer value of the current address of that thing. However, there are also compacting garbage collectors, which move live things around during garbage collection for various reasons. In these garbage collectors, the memory address of things is not stable.
Go is deliberately specified so that you could implement it using
a compacting GC, and at one point this was the long term plan. When it moved things as part
of garbage collection, such a Go would update the address of actual
pointers to them to the new value. However, it would not magically
update integer values derived from those pointers, whether they're
uintptr
s or some other integer types. In a compacting GC world,
getting the uintptr
of the address of something twice at different
times could give you two different values. Each value was accurate
at the moment you got it, but it's not guaranteed to be accurate
one instant past that; a GC pass could happen at any time and thus
the thing could be moved at any time.
Leaving the door open for a compacting GC is one of the reasons
that the rules surrounding the use of unsafe.Pointer()
and uintptr
are so
carefully and narrowly specified, as we've seen before. In fact the documentation points this out
explicitly:
A uintptr is an integer, not a reference. Converting a Pointer to a uintptr creates an integer value with no pointer semantics. Even if a uintptr holds the address of some object, the garbage collector will not update that uintptr's value if the object moves, nor will that uintptr keep the object from being reclaimed.
(The emphasis is mine.)
The Go garbage collector never moves things today, which leads to the practical answer for today of 'yes, you can do this'. But the theoretical answer is that the address of things could be constantly changing, and maybe someday in the future they sometimes will.
Update: As pointed out in the r/golang comments on my entry, I'm wrong here. In Go today, stacks are movable as stack usage grows and shrinks, and you can take the address of a value that is on the stack and that subsequently gets moved with the stack.
|
|