No Go runtime/proc.go
, há um trecho de código mostrado abaixo:
// funcPC retorna a entrada PC da função f.
// Supõe que f é um valor de função. Caso contrário, o comportamento é indefinido.
// CUIDADO: Nos programas com plugins, o funcPC pode retornar valores diferentes
// para a mesma função (porque na verdade existem várias cópias da
// mesma função no espaço de endereço). Para estar seguro, não use os
// resultados desta função em nenhuma expressão ==. É seguro
// usar o resultado como um endereço no qual iniciar a execução do código.
//go:nosplit
func funcPC(f interface{}) uintptr {
return **(**uintptr)(add(unsafe.Pointer(&f), sys.PtrSize))
}
O que eu não entendo é por que não usar * (* uintptr) em vez de ** (** uintptr)?
Então, escrevo um programa de teste abaixo para descobrir.
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))
}
O resultado de que p não é igual a p1 me deixa confusa. Por que o valor de p não é igual ao valor de p1 enquanto o tipo é o mesmo?
funcPC(p)
seria o valor . Qual é o sentido de ter um ponteiro para um ponteiro de qualquer maneira.pp
apontar parap
, escrevendo para*pp
gravaçõesp
e lendo*pp
leituras dep
. Sep
estiver no escopo, é claro que isso é um pouco bobo, já que você pode apenas ler ou escreverp
diretamente. Mas e se nãop
estiver no escopo, ou e se apontar para um ou (dependendo da lógica anterior), e você gostaria de usar ou atualizar o ponteiro que aponta para?pp
p
q
pp
Respostas:
Introdução
Um valor da função em Go indica o código da função. De longe, é um ponteiro para o código da função. Funciona como um ponteiro.
De um olhar mais atento, é uma estrutura mais ou menos assim (tirada de
runtime/runtime2.go
):Portanto, um valor de função mantém um ponteiro para o código da função como seu primeiro campo, o qual podemos desreferenciar para obter o código da função.
Explicando seu exemplo
Para obter o endereço de uma função (código de), você pode usar a reflexão:
Para verificar se obtemos o endereço certo, podemos usar
runtime.FuncForPC()
.Isso fornece o mesmo valor que sua
funcPC()
função. Veja este exemplo:Ele produz (experimente no Go Playground ):
Por quê? Como um valor de função em si é um ponteiro (ele apenas tem um tipo diferente de um ponteiro, mas o valor que ele armazena é um ponteiro) que precisa ser desreferenciado para obter o endereço do código .
Então, o que você precisaria para obter isso
uintptr
(endereço de código) dentrofuncPC()
seria simplesmente:Claro que não compila, as regras de conversão não permitem converter um valor de função em
*uintptr
.Outra tentativa pode ser convertê-lo primeiro em
unsafe.Pointer
e depois em*uintptr
:Novamente: as regras de conversão não permitem converter valores de função em
unsafe.Pointer
. Qualquer tipo de ponteiro euintptr
valores podem ser convertidosunsafe.Pointer
e vice-versa, mas não os valores da função.É por isso que precisamos ter um valor de ponteiro para começar. E que valor de ponteiro poderíamos ter? Sim, o endereço
f
:&f
. Mas este não será o valor da função, este é o endereço dof
parâmetro (variável local). Portanto,&f
esquematicamente não é (apenas) um ponteiro, é um ponteiro para um ponteiro (que ambos precisam ser desreferenciados). Ainda podemos convertê-lo paraunsafe.Pointer
(porque qualquer valor de ponteiro se qualifica para isso), mas não é o valor da função (como ponteiro), mas um ponteiro para ele.E precisamos do endereço de código do valor da função, portanto, temos que usar
**uintptr
para converter ounsafe.Pointer
valor e usar 2 desreferências para obter o endereço (e não apenas o ponteirof
).É exatamente por isso que
funcPC1()
fornece um resultado diferente, inesperado e incorreto:Retorna o ponteiro
f
, não o endereço do código real.fonte
conversion rules
sobre não permitir a conversão de umfunction value
parauintptr
.x
pode ser convertido para digitarT
em qualquer um desses casos ..."Ele retorna um valor diferente porque ** (** uintptr) não é o mesmo que * (* uintptr). O primeiro é um indireto duplo, o posterior um indireto simples.
No primeiro caso, o valor é um ponteiro para um ponteiro para um ponteiro para uma uint.
fonte