Converting a Go pointer to an integer doesn't quite do what it looks like

September 18, 2019

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 uintptrs 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.

Written on 18 September 2019.
« Finding metrics that are missing labels in Prometheus (for alert metrics)
Firefox, DNS over HTTPS, and us »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Wed Sep 18 00:32:58 2019
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.