簡體   English   中英

*(*uintptr) 和 **(**uintptr) 有什么區別

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM