代码之家  ›  专栏  ›  技术社区  ›  river

解释方法表达式的打印值

  •  -1
  • river  · 技术社区  · 6 年前

    以下代码尝试显示与结构关联的方法的地址。

    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 回复  |  直到 6 年前
        1
  •  3
  •   Cerise Limón    6 年前

    这个 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
    

    这与问题中显示的模式匹配。

    这个 function related code for 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 通过创建 method expression 在反射API中 flagMethod 方法位集。正如注释状态和代码所示,指针方法为以这种方式创建的所有方法表达式返回相同的值。

    这个 反映价值 创建的 relect.ValueOf(s1.Callme) 没有 旗杆法 方法位集。在这种情况下,函数返回指向实际代码的指针。

    产量 this program 显示所有组合:

    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
    

    我们可以观察到这个值。指针返回的所有值都相同 method expressions 通过反射API创建。这包括不同类型的方法。

    我们也可以观察到 指针值 返回相同的值 方法表达式 在给定的类型和方法上。对于绑定到不同值的方法表达式,这是正确的。

    这个 Value.Pointer documentation 说:

    如果v的kind是func,则返回的指针是一个基础代码指针,但不一定足以唯一地标识单个函数。唯一的保证是当且仅当v为零func值时,结果为零。

    鉴于此,应用程序无法可靠地使用value.pointer或通过 FMT 用于比较函数和方法的包。