Qual é a diferença entre * (* uintptr) e ** (** uintptr)

8

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

insira a descrição da imagem aqui

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?

polar9527
fonte
1
Porque é um ponteiro para um ponteiro?
Zerkms 28/11/19
Não sei Go, mas me pergunto qual funcPC(p)seria o valor . Qual é o sentido de ter um ponteiro para um ponteiro de qualquer maneira.
LukStorms 28/11/19
@LukStorms: o, erm, ponto, de um ponteiro para um ponteiro, é apontar para o ponteiro. Se ppapontar para p, escrevendo para *ppgravações pe lendo *ppleituras de p. Se pestiver no escopo, é claro que isso é um pouco bobo, já que você pode apenas ler ou escrever pdiretamente. 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 qpp
Torek 28/11/19
@ Torek Oh ok, então talvez não seja tão inútil, afinal.
LukStorms

Respostas:

8

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

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

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:

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

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:

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

Ele produz (experimente no Go Playground ):

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

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) dentro funcPC()seria simplesmente:

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

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.Pointere depois em *uintptr:

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

Novamente: as regras de conversão não permitem converter valores de função em unsafe.Pointer. Qualquer tipo de ponteiro e uintptrvalores podem ser convertidos unsafe.Pointere 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 do fparâmetro (variável local). Portanto, &fesquematicamente não é (apenas) um ponteiro, é um ponteiro para um ponteiro (que ambos precisam ser desreferenciados). Ainda podemos convertê-lo para unsafe.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 **uintptrpara converter o unsafe.Pointervalor e usar 2 desreferências para obter o endereço (e não apenas o ponteiro f).

É exatamente por isso que funcPC1()fornece um resultado diferente, inesperado e incorreto:

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

Retorna o ponteiro f, não o endereço do código real.

icza
fonte
1
Não consigo encontrar nenhuma informação conversion rulessobre não permitir a conversão de um function valuepara uintptr.
polar9527
3
@ polar9527 Exatamente porque não há. O que é permitido é explicitamente listado nas especificações, e o que não é lá não é permitido. Procure a lista que começa com: "Um valor não constante xpode ser convertido para digitar Tem qualquer um desses casos ..."
icza 28/11/19
0

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.

chmike
fonte
2
Gostaria de explicar qual é a diferença entre ** (** uintptr) e justamente (uintptr)? Por que essa sintaxe ** (** uintptr) está sendo usada aqui?
Minitauros