Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between *(*uintptr) and **(**uintptr)

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))
}

enter image description here

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?

like image 332
polar9527 Avatar asked Nov 28 '19 08:11

polar9527


Video Answer


1 Answers

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.

like image 86
icza Avatar answered Oct 16 '22 18:10

icza