[英]What is the difference between *(*uintptr) and **(**uintptr)
在 Go 的runtime/proc.go
中,有一段代碼如下所示:
// funcPC 返回函數 f 的入口 PC。
// 它假設 f 是一個 func 值。 否則行為未定義。
// 注意:在帶有插件的程序中,funcPC 可以返回不同的值
// 對於同一個函數(因為實際上有多個副本
//地址空間中的相同函數)。 為了安全起見,請勿使用
// 這個函數在任何 == 表達式中的結果。 只有這樣才安全
// 使用結果作為開始執行代碼的地址。
//go:nosplit
func funcPC(f interface{}) uintptr {
return **(**uintptr)(add(unsafe.Pointer(&f), sys.PtrSize))
}
我不明白的是為什么不使用 *(*uintptr) 而不是 **(**uintptr)?
所以我在下面寫了一個測試程序來搞清楚。
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))
}
p 不等於 p1 的結果讓我感到困惑。 為什么 p 的值不等於 p1 的值,而它們的類型相同?
Go 中的函數值表示函數的代碼。 從遠處看,它是指向函數代碼的指針。 它就像一個指針。
仔細看,它是一個類似這樣的結構(取自runtime/runtime2.go
):
type funcval struct {
fn uintptr
// variable-size, fn-specific data here
}
因此,函數值將指向函數代碼的指針作為其第一個字段,我們可以取消引用以獲取函數代碼。
要獲取函數(代碼)的地址,您可以使用反射:
fmt.Println("test() address:", reflect.ValueOf(test).Pointer())
為了驗證我們獲得了正確的地址,我們可以使用runtime.FuncForPC()
。
這給出與您的funcPC()
函數相同的值。 看這個例子:
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())
它輸出(在Go Playground上試試):
reflection test() address: 919136
funcPC(test): 919136
funcPC1(test): 1357256
func name for reflect ptr: main.test
為什么? 因為函數值本身就是一個指針(只是和指針的類型不同,但它存儲的值是指針)需要解引用才能得到代碼地址。
因此,您需要將其獲取到uintptr
() 內的funcPC()
(代碼地址),這很簡單:
func funcPC(f func()) uintptr {
return *(*uintptr)(f) // Compiler error!
}
當然它不會編譯,轉換規則不允許將函數值轉換為*uintptr
。
另一種嘗試可能是先將其轉換為unsafe.Pointer
,然后再轉換為*uintptr
:
func funcPC(f func()) uintptr {
return *(*uintptr)(unsafe.Pointer(f)) // Compiler error!
}
同樣:轉換規則不允許將函數值轉換為unsafe.Pointer
。 任何指針類型和uintptr
值都可以轉換為unsafe.Pointer
,反之亦然,但不能轉換為函數值。
這就是為什么我們必須有一個指針值作為開始。 我們可以擁有什么指針值? 是的, f
的地址: &f
。 但這不會是函數值,這是f
參數(局部變量)的地址。 所以&f
示意性地不是(只是)一個指針,它是一個指向指針的指針(兩者都需要取消引用)。 我們仍然可以將它轉換為unsafe.Pointer
(因為任何指針值都符合此條件),但它不是函數值(作為指針),而是指向它的指針。
而且我們需要函數值中的代碼地址,所以我們必須使用**uintptr
來轉換unsafe.Pointer
值,並且我們必須使用 2 個解引用來獲取地址(而不僅僅是f
中的指針)。
這正是funcPC1()
給出不同的、意外的、不正確的結果的原因:
func funcPC1(f func()) uintptr {
return *(*uintptr)(unsafe.Pointer(&f))
}
它返回f
中的指針,而不是實際的代碼地址。
它返回不同的值,因為 **(**uintptr) 與 *(*uintptr) 不同。 前者是雙重間接,后者是簡單間接。
在前一種情況下,值是指向 uint 指針的指針。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.