*(* uintptr)と**(** uintptr)の違いは何ですか


8

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の値と等しくないのはなぜですか?


1
ポインタへのポインタだから?
zerkms

囲碁はわかりませんが、どんな価値があるのfuncPC(p)でしょうか。とにかく、ポインタへのポインタを持つことの意味は何ですか。
LukStorms

@LukStorms:ポインタを指すポインタのerm、pointは、ポインタを指すことです。をpp指す場合はp、への*pp書き込みpおよびからの*pp読み取りからの読み取りppが範囲内にある場合は、p直接読み取ることも、直接書き込むこともできるため、これはもちろん少しばかげています。しかし、どのような場合pない範囲で、あるいはどのような場合ppのポイントへのいずれか p または q(それ以前の論理に応じて)、あなたが使用したいか更新どちらポインタうppにポイント?
torek

@torekああ、それで結局は無意味ではないかもしれません。
LukStorms

回答:


8

前書き

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実際のコードアドレスではなく、ポインタを返します。


1
私は内の任意の情報を見つけることができません conversion rules変換することができない程度function valueにしますuintptr
polar9527

3
@ polar9527ないからです。許可されているものは仕様に明示的にリストされており、許可されていないものは許可されていません。「非定数値xT、これらのいずれの場合でも型に変換できる...」で
icza

0

**(** uintptr)は*(* uintptr)と同じではないため、異なる値を返します。前者は二重の間接参照であり、後者は単純な間接参照です。

前者の場合、値はuintへのポインターへのポインターへのポインターです。


2
**(** uintptr)と(uintptr)の違いは何ですか?なぜこの**(** uintptr)構文がここで使用されているのですか?
minitauros
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.