What is the difference between *(*uintptr) and **(**uintptr)
Introduction
A function value in Go denotes the funtion's code. From far, it is a pointer to the function's code. It acts like a pointer.
From a closer look, it's a struct something like this (taken from runtime/runtime2.go
):
type funcval struct {
fn uintptr
// variable-size, fn-specific data here
}
So a function value holds a pointer to the function's code as its first field which we can dereference to get to the function's code.
Explaining your example
To get the address of a function('s code), you may use reflection:
fmt.Println("test() address:", reflect.ValueOf(test).Pointer())
To verify we get the right address, we may use runtime.FuncForPC()
.
This gives the same value as your funcPC()
function. See this example:
fmt.Println("reflection test() address:", reflect.ValueOf(test).Pointer())
fmt.Println("funcPC(test):", funcPC(test))
fmt.Println("funcPC1(test):", funcPC1(test))
fmt.Println("func name for reflect ptr:",
runtime.FuncForPC(reflect.ValueOf(test).Pointer()).Name())
It outputs (try it on the Go Playground):
reflection test() address: 919136
funcPC(test): 919136
funcPC1(test): 1357256
func name for reflect ptr: main.test
Why? Because a function value itself is a pointer (it just has a different type than a pointer, but the value it stores is a pointer) that needs to be dereferenced to get the code address.
So what you would need to get this to uintptr
(code address) inside funcPC()
would be simply:
func funcPC(f func()) uintptr {
return *(*uintptr)(f) // Compiler error!
}
Of course it doesn't compile, conversion rules do not allow converting a function value to *uintptr
.
Another attempt may be to convert it first to unsafe.Pointer
, and then to *uintptr
:
func funcPC(f func()) uintptr {
return *(*uintptr)(unsafe.Pointer(f)) // Compiler error!
}
Again: conversion rules do not allow converting function values to unsafe.Pointer
. Any pointer type and uintptr
values may be converted to unsafe.Pointer
and vice versa, but not function values.
That's why we have to have a pointer value to start with. And what pointer value could we have? Yes, the address of f
: &f
. But this will not be the function value, this is the address of the f
parameter (local variable). So &f
schematically is not (just) a pointer, it's a pointer to pointer (that both need to be dereferenced). We can still convert it to unsafe.Pointer
(because any pointer value qualifies for that), but it's not the function value (as a pointer), but a pointer to it.
And we need the code address from the function value, so we have to use **uintptr
to convert the unsafe.Pointer
value, and we have to use 2 dereferences to get the address (and not just the pointer in f
).
This is exactly why funcPC1()
gives a different, unexpected, incorrect result:
func funcPC1(f func()) uintptr {
return *(*uintptr)(unsafe.Pointer(&f))
}
It returns the pointer in f
, not the actual code address.