Объясните напечатанные значения выражений метода

Следующий код пытается показать адрес метода, связанного со структурой.

package  main

import (
    "fmt"
    "reflect"
)

type II interface {
    Callme()
}

type Str struct {
    I int
    S string
}

func (s *Str) Callme () {
    fmt.Println("it is me")
}

func main() {
    s0 := &Str{}
    t := reflect.TypeOf(s0)
    v := reflect.ValueOf(s0)
    fmt.Println("Callme ", s0.Callme)   //real address ?
    fmt.Println(t.Method(0).Name, v.Method(0))    //real address ?

    s1 := &Str{}
    t1 := reflect.TypeOf(s1)
    v1 := reflect.ValueOf(s1)
    fmt.Println("Callme ", s1.Callme)   //real address ?
    fmt.Println(t1.Method(0).Name, v1.Method(0))    //real address ?
}

Выход:

Callme  0x4bc2d0
Callme 0x4ab2c0
Callme  0x4bc2d0
Callme 0x4ab2c0

Итак, у меня есть два вопроса:

  • Во-первых, почему эти утверждения не показывают одинаковые значения?

    fmt.Println("Callme ", s0.Callme)
    fmt.Println(t.Method(0).Name, v.Method(0))
    
  • Во-вторых, почему эти утверждения показывают одинаковые значения?

    fmt.Println(t.Method(0).Name, v.Method(0))    
    fmt.Println(t1.Method(0).Name, v1.Method(0)) 
    

1 ответ

Решение

fmt пакет вызывает Value.Pointer для получения адресов функций.

Давайте посмотрим на пример того, что Value.Pointer возвращает для функций:

s0 := &Str{}
v0 := reflect.ValueOf(s0)
fmt.Printf("s0.Callme: %0x %0x\n", reflect.ValueOf(s0.Callme).Pointer(), s0.Callme)
fmt.Printf("v0.Method(0) %0x %0x\n", v0.Method(0).Pointer(), v0.Method(0))

s1 := &Str{}
v1 := reflect.ValueOf(s1)
fmt.Printf("s1.Callme %x %x\n", reflect.ValueOf(s1.Callme).Pointer(), s1.Callme)
fmt.Printf("v1.Method(0) %x %x\n", v1.Method(0).Pointer(), v1.Method(0))

Выход:

s0.Callme: 105240 105240
v0.Method(0) eee60 eee60
s1.Callme 105240 105240
v1.Method(0) eee60 eee60

Это соответствует схеме, показанной в вопросе.

Код, связанный с функцией для Value.Pointer:

    if v.flag&flagMethod != 0 {
        // As the doc comment says, the returned pointer is an
        // underlying code pointer but not necessarily enough to
        // identify a single function uniquely. All method expressions
        // created via reflect have the same underlying code pointer,
        // so their Pointers are equal. The function used here must
        // match the one used in makeMethodValue.
        f := methodValueCall
        return **(**uintptr)(unsafe.Pointer(&f))
    }
    p := v.pointer()
    // Non-nil func value points at data block.
    // First word of data block is actual code.
    if p != nil {
        p = *(*unsafe.Pointer)(p)
    }
    return uintptr(p)

reflect.Value созданный с помощью выражения метода в отражающем API имеет flagMethod бит метода установлен. Как отмечается в комментарии и показывается код, метод Pointer возвращает одинаковое значение для всех выражений метода, созданных таким образом.

reflect.Value создано relect.ValueOf(s1.Callme) не имеет flagMethod бит метода установлен. В этом случае функция возвращает указатель на фактический код.

Выходные данные этой программы показывают все комбинации:

type StrA struct {
    I int
    S string
}

func (s *StrA) Callme() {
    fmt.Println("it is me")
}

type StrB struct {
    I int
    S string
}

func (s *StrB) Callme() {
    fmt.Println("it is me")
}

s0A := &StrA{}
v0A := reflect.ValueOf(s0A)
s1A := &StrA{}
v1A := reflect.ValueOf(s0A)

fmt.Println("s0A.Callme ", reflect.ValueOf(s0A.Callme).Pointer())
fmt.Println("v0A.Method(0) ", v0A.Method(0).Pointer())
fmt.Println("s1A.Callme ", reflect.ValueOf(s1A.Callme).Pointer())
fmt.Println("v1A.Method(0) ", v1A.Method(0).Pointer())

s0B := &StrB{}
v0B := reflect.ValueOf(s0B)
s1B := &StrB{}
v1B := reflect.ValueOf(s0B)

fmt.Println("s0B.Callme ", reflect.ValueOf(s0B.Callme).Pointer())
fmt.Println("v0B.Method(0) ", v0B.Method(0).Pointer())
fmt.Println("s1B.Callme ", reflect.ValueOf(s1B.Callme).Pointer())
fmt.Println("v1B.Method(0) ", v1B.Method(0).Pointer())

Выход:

s0A.Callme  1061824
v0A.Method(0)  978528
s1A.Callme  1061824
v1A.Method(0)  978528
s0B.Callme  1061952
v0B.Method(0)  978528
s1B.Callme  1061952
v1B.Method(0)  978528

Мы можем заметить, что Value.Pointer возвращает одно и то же значение для всех выражений методов, созданных через отражающий API. Это включает в себя методы разных типов.

Мы также можем наблюдать, что Value.Pointer возвращает одно и то же значение для всех выражений метода для данного типа и метода. Это верно для выражений метода, связанных с различными значениями.

Документация Value.Pointer гласит:

Если v для Kind - Func, возвращаемый указатель является указателем на базовый код, но этого не обязательно достаточно для уникальной идентификации одной функции. Единственная гарантия состоит в том, что результат равен нулю тогда и только тогда, когда v является нулевым значением Func.

Учитывая это, приложение не может надежно использовать Value.Pointer или печатные значения через fmt пакет для сравнения функций и методов.

Другие вопросы по тегам