¿Cuál es la diferencia entre * (* uintptr) y ** (** uintptr)

8

En Go's runtime/proc.go, hay un código que se muestra a continuación:

// funcPC devuelve la PC de entrada de la función f.
// Se supone que f es un valor func. De lo contrario, el comportamiento es indefinido.
// CUIDADO: en programas con complementos, funcPC puede devolver diferentes valores
// para la misma función (porque en realidad hay múltiples copias de
// la misma función en el espacio de direcciones). Para estar seguro, no use los
// resultados de esta función en ninguna expresión ==. Solo es seguro
// usar el resultado como una dirección para comenzar a ejecutar el código.

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

Lo que no entiendo es ¿por qué no usar * (* uintptr) en lugar de ** (** uintptr)?

Entonces escribo un programa de prueba a continuación para descubrir.

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

ingrese la descripción de la imagen aquí

El resultado de que p no es igual a p1 me confunde. ¿Por qué el valor de p no es igual al valor de p1 mientras su tipo es el mismo?

polar9527
fuente
1
¿Porque es un puntero a un puntero?
zerkms
No sé Go, pero me pregunto cuál funcPC(p)sería el valor de . ¿Cuál es el punto de tener un puntero a un puntero de todos modos?
LukStorms
@LukStorms: el, erm, punto, de un puntero a un puntero, es apuntar al puntero. Si ppapunta a p, escribir a *ppescribe py leer de *pplecturas de p. Si pestá dentro del alcance, eso es, por supuesto, un poco tonto, ya que podría leer o escribir pdirectamente. Pero, ¿qué ppasa si no está dentro del alcance, o qué pasa si ppapunta a cualquiera p o q (según la lógica anterior), y le gustaría usar o actualizar el puntero al que ppapunta?
torek
@torek Oh ok, entonces quizás no sea tan inútil después de todo.
LukStorms el

Respuestas:

8

Introducción

Un valor de función en Go denota el código de la función. Desde lejos, es un puntero al código de la función. Actúa como un puntero.

De un vistazo más de cerca, es una estructura similar a esta (tomada de runtime/runtime2.go):

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

Por lo tanto, el valor de una función contiene un puntero al código de la función como su primer campo que podemos desreferenciar para llegar al código de la función.

Explicando tu ejemplo

Para obtener la dirección de una función (código de '), puede usar la reflexión:

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

Para verificar que obtenemos la dirección correcta, podemos usar runtime.FuncForPC().

Esto le da el mismo valor que su funcPC()función. Ver este ejemplo:

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

Produce (pruébalo en Go Playground ):

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

¿Por qué? Debido a que el valor de una función en sí mismo es un puntero (solo tiene un tipo diferente que un puntero, pero el valor que almacena es un puntero) que debe desreferenciarse para obtener la dirección del código .

Entonces, lo que necesitaría para obtener esto uintptr(dirección de código) dentro funcPC()sería simplemente:

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

Por supuesto que no se compila, reglas de conversión no permiten la conversión de un valor de función a *uintptr.

Otro intento puede ser convertirlo primero a unsafe.Pointer, y luego a *uintptr:

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

Nuevamente: las reglas de conversión no permiten convertir valores de función a unsafe.Pointer. Cualquier tipo de puntero y uintptrvalores se pueden convertir unsafe.Pointery viceversa, pero no valores de función.

Es por eso que tenemos que tener un valor de puntero para comenzar. ¿Y qué valor de puntero podríamos tener? Sí, la dirección de f: &f. Pero este no será el valor de la función, esta es la dirección del fparámetro (variable local). Entonces, &fesquemáticamente no es (solo) un puntero, es un puntero a puntero (ambos deben ser desreferenciados). Todavía podemos convertirlo a unsafe.Pointer(porque cualquier valor de puntero califica para eso), pero no es el valor de la función (como puntero), sino un puntero a él.

Y necesitamos la dirección del código del valor de la función, por lo que tenemos que usar **uintptrpara convertir el unsafe.Pointervalor, y tenemos que usar 2 desreferencias para obtener la dirección (y no solo el puntero f).

Esto es exactamente por qué funcPC1()da un resultado diferente, inesperado e incorrecto:

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

Devuelve el puntero f, no la dirección del código real.

icza
fuente
1
No puedo encontrar ninguna información conversion rulesacerca de no permitir la conversión de function valuea uintptr.
polar9527
3
@ polar9527 Exactamente porque no lo hay. Lo que está permitido se enumera explícitamente en la especificación, y lo que no está allí no está permitido. Busque la lista que comienza con: "Un valor no constante xse puede convertir para escribir Ten cualquiera de estos casos ..."
icza
0

Devuelve un valor diferente porque ** (** uintptr) no es lo mismo que * (* uintptr). La primera es una doble indirección, la segunda una simple indirección.

En el primer caso, el valor es un puntero a un puntero a un puntero a un uint.

chmike
fuente
2
¿Te gustaría explicar cuál es la diferencia entre ** (** uintptr) y solo (uintptr)? ¿Por qué se usa esta sintaxis ** (** uintptr) aquí?
minitauros