In Go's runtime/proc.go
wird 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))
}
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?
funcPC(p)
wäre. Was bringt es überhaupt, einen Zeiger auf einen Zeiger zu haben?pp
verweistp
, Schreiben in*pp
Schreibenp
und Lesen aus*pp
Lesen vonp
. Wennp
es im Umfang liegt, ist das natürlich ein bisschen albern, da man einfach direkt daraus lesen oder darauf schreiben kannp
. Aber was , wennp
ist nicht in ihrem Umfang, oder was passiert , wennpp
Punkte entwederp
oderq
(je nach früheren Logik), und Sie möchten , verwenden oder zu aktualisieren je nachdem , welche Zeigerpp
verweisen auf?Antworten:
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
):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:
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:Es gibt aus (versuchen Sie es auf dem Go Playground ):
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: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.Pointer
und dann in Folgendes umzuwandeln*uintptr
:Nochmals: Konvertierungsregeln erlauben keine Konvertierung von Funktionswerten in
unsafe.Pointer
. Jeder Zeigertyp und alleuintptr
Werte können inunsafe.Pointer
und 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 von
f
:&f
. Dies ist jedoch nicht der Funktionswert, sondern die Adresse desf
Parameters (lokale Variable). So&f
schematisch ist nicht (nur) ein Zeiger, dann ist es ein Zeiger auf Zeiger (dass beide brauchen wird dereferenziert). Wir können es immer noch in konvertierenunsafe.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 denunsafe.Pointer
Wert zu konvertieren , und wir müssen 2 Dereferenzen verwenden, um die Adresse zu erhalten (und nicht nur den Zeiger inf
).Genau deshalb
funcPC1()
ergibt sich ein anderes, unerwartetes und falsches Ergebnis:Es gibt den Zeiger zurück
f
, nicht die tatsächliche Code-Adresse.quelle
conversion rules
darüber finden, dass das Konvertieren von afunction value
in nicht zulässig istuintptr
.x
kannT
in jedem dieser Fälle in Typ konvertiert werden ..."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.
quelle