Was ist der Unterschied zwischen * (* uintptr) und ** (** uintptr)

8

In Go's runtime/proc.gowird unten ein Code gezeigt:

// funcPC gibt den Eingabe-PC der Funktion f zurück.
// Es wird davon ausgegangen, dass f ein Funktionswert ist. Ansonsten ist das Verhalten undefiniert.
// VORSICHT: In Programmen mit Plugins kann funcPC unterschiedliche
// Werte für dieselbe Funktion zurückgeben (da sich tatsächlich mehrere Kopien
derselben Funktion im Adressraum befinden). Verwenden Sie aus Sicherheitsgründen die
// Ergebnisse dieser Funktion in keinem == Ausdruck. Es ist nur sicher,
// das Ergebnis als Adresse zu verwenden, an der mit der Ausführung von Code begonnen werden soll.

//go:nosplit
func funcPC(f interface{}) uintptr {
    return **(**uintptr)(add(unsafe.Pointer(&f), sys.PtrSize))
}

Was ich nicht verstehe ist, warum nicht * (* uintptr) anstelle von ** (** uintptr) verwenden?

Also schreibe ich unten ein Testprogramm, um es herauszufinden.

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

Geben Sie hier die Bildbeschreibung ein

Das Ergebnis, dass p nicht gleich p1 ist, macht mich verwirrt. Warum ist der Wert von p nicht gleich dem Wert von p1, während ihr Typ gleich ist?

polar9527
quelle
1
Weil es ein Zeiger auf einen Zeiger ist?
Zerkms
Ich weiß nicht, Go, aber ich frage mich, was der Wert von funcPC(p)wäre. Was bringt es überhaupt, einen Zeiger auf einen Zeiger zu haben?
LukStorms
@LukStorms: Der Punkt eines Zeigers auf einen Zeiger soll auf den Zeiger zeigen. Wenn darauf ppverweist p, Schreiben in *ppSchreiben pund Lesen aus *ppLesen von p. Wennp es im Umfang liegt, ist das natürlich ein bisschen albern, da man einfach direkt daraus lesen oder darauf schreiben kann p. Aber was , wenn pist nicht in ihrem Umfang, oder was passiert , wenn ppPunkte entweder p oder q (je nach früheren Logik), und Sie möchten , verwenden oder zu aktualisieren je nachdem , welche Zeiger ppverweisen auf?
Torek
@torek Oh ok, also vielleicht doch nicht so sinnlos.
LukStorms

Antworten:

8

Einführung

Ein Funktionswert in Go bezeichnet den Funktionscode. Von weitem ist es ein Zeiger auf den Funktionscode. Es wirkt wie ein Zeiger.

Bei näherer Betrachtung handelt es sich um eine Struktur wie diese (entnommen aus runtime/runtime2.go):

type funcval struct {
    fn uintptr
    // variable-size, fn-specific data here
}

Ein Funktionswert enthält also einen Zeiger auf den Funktionscode als erstes Feld, das wir dereferenzieren können, um zum Funktionscode zu gelangen.

Erklären Sie Ihr Beispiel

Um die Adresse einer Funktion (des Codes) abzurufen, können Sie Reflection verwenden:

fmt.Println("test() address:", reflect.ValueOf(test).Pointer())

Um zu überprüfen, ob wir die richtige Adresse erhalten, können wir verwenden runtime.FuncForPC().

Dies ergibt den gleichen Wert wie Ihre funcPC()Funktion. Siehe dieses Beispiel:

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

Es gibt aus (versuchen Sie es auf dem Go Playground ):

reflection test() address: 919136
funcPC(test): 919136
funcPC1(test): 1357256
func name for reflect ptr: main.test

Warum? Da ein Funktionswert selbst ein Zeiger ist (er hat nur einen anderen Typ als ein Zeiger, aber der darin gespeicherte Wert ist ein Zeiger) , muss dereferenziert werden, um die Codeadresse zu erhalten .

Was Sie also benötigen würden, um dies uintptr(Code-Adresse) in sich aufzunehmen, funcPC()wäre einfach:

func funcPC(f func()) uintptr {
    return *(*uintptr)(f) // Compiler error!
}

Natürlich wird es nicht kompiliert, Konvertierungsregeln erlauben keine Konvertierung eines Funktionswerts in *uintptr.

Ein weiterer Versuch könnte darin bestehen, es zuerst in unsafe.Pointerund dann in Folgendes umzuwandeln *uintptr:

func funcPC(f func()) uintptr {
    return *(*uintptr)(unsafe.Pointer(f)) // Compiler error!
}

Nochmals: Konvertierungsregeln erlauben keine Konvertierung von Funktionswerten in unsafe.Pointer. Jeder Zeigertyp und alle uintptrWerte können in unsafe.Pointerund umgekehrt konvertiert werden , jedoch keine Funktionswerte.

Deshalb müssen wir zunächst einen Zeigerwert haben. Und welchen Zeigerwert könnten wir haben? Ja, die Adresse vonf : &f. Dies ist jedoch nicht der Funktionswert, sondern die Adresse des fParameters (lokale Variable). So &fschematisch ist nicht (nur) ein Zeiger, dann ist es ein Zeiger auf Zeiger (dass beide brauchen wird dereferenziert). Wir können es immer noch in konvertieren unsafe.Pointer(weil jeder Zeigerwert dafür qualifiziert ist), aber es ist nicht der Funktionswert (als Zeiger), sondern ein Zeiger darauf.

Und wir brauchen die Code-Adresse aus dem Funktionswert, also müssen wir verwenden **uintptr, um den unsafe.PointerWert zu konvertieren , und wir müssen 2 Dereferenzen verwenden, um die Adresse zu erhalten (und nicht nur den Zeiger in f).

Genau deshalb funcPC1() ergibt sich ein anderes, unerwartetes und falsches Ergebnis:

func funcPC1(f func()) uintptr {
    return *(*uintptr)(unsafe.Pointer(&f))
}

Es gibt den Zeiger zurück f, nicht die tatsächliche Code-Adresse.

icza
quelle
1
Ich kann keine Informationen conversion rulesdarüber finden, dass das Konvertieren von a function valuein nicht zulässig ist uintptr.
Polar9527
3
@ polar9527 Genau weil es keine gibt. Was erlaubt ist, ist in der Spezifikation explizit aufgeführt, und was nicht da ist, ist nicht erlaubt. Suchen Sie nach der Liste beginnend mit: "Ein nicht konstanter Wert xkann Tin jedem dieser Fälle in Typ konvertiert werden ..."
icza
0

Es wird ein anderer Wert zurückgegeben, da ** (** uintptr) nicht mit * (* uintptr) identisch ist. Ersteres ist eine doppelte Indirektion, später eine einfache Indirektion.

Im ersteren Fall ist der Wert ein Zeiger auf einen Zeiger auf einen Zeiger auf eine Uint.

chmike
quelle
2
Möchten Sie erklären, was der Unterschied zwischen ** (** uintptr) und nur (uintptr) ist? Warum wird hier diese ** (** uintptr) -Syntax verwendet?
Minitauros