In Go's runtime/proc.go
, there is a piece of code showed below:
// funcPC returns the entry PC of the function f.
// It assumes that f is a func value. Otherwise the behavior is undefined.
// CAREFUL: In programs with plugins, funcPC can return different values
// for the same function (because there are actually multiple copies of
// the same function in the address space). To be safe, don't use the
// results of this function in any == expression. It is only safe to
// use the result as an address at which to start executing code.
//go:nosplit
func funcPC(f interface{}) uintptr {
return **(**uintptr)(add(unsafe.Pointer(&f), sys.PtrSize))
}
What I don't understand is why not use *(*uintptr) instead of **(**uintptr)?
So I write a test program below to figure out.
package main
import (
"fmt"
"unsafe"
)
func main(){
fmt.Println()
p := funcPC(test)
fmt.Println(p)
p1 := funcPC1(test)
fmt.Println(p1)
p2 := funcPC(test)
fmt.Println(p2)
}
func test(){
fmt.Println("hello")
}
func funcPC(f func()) uintptr {
return **(**uintptr)(unsafe.Pointer(&f))
}
func funcPC1(f func()) uintptr {
return *(*uintptr)(unsafe.Pointer(&f))
}
The result that p doesn't equal p1 makes me confused. Why doesn't the value of p equal the value of p1 while their type is the same?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With